My problem is when I have more than one object in the state every item of the list re-renders and they each have a deletion timeout (I want them to be deleted after an amount of time) which is reset at each re-render.
I tried to use React.memo() and React.useCallback() to prevent the item in the list to re-render when adding new items but since I recreate the array each time I add or remove a todo the values passed to list items are renewed and the items re-render.
I tried like in the code example to pass as props only values of the items and not an entire object but that not changed anything
This is a gif to illustrate my problem, but you have a code Sandbox too (just below) to see by yourself
In the first part of the video everything is going well
but when I click 2 times I expect the items to get deleted 3s after getting rendered, but only the last item gets deleted then the list disappears then the first is deleted
Here is a code Sandbox with the code below inside
index.js
import { useState } from "react"; import ReactDOM from "react-dom/client"; import Todos from "./Todos"; const App = () => { const [todos, setTodos] = useState([]); const addTodo = () => { setTodos((todos) => [ ...todos, { id: new Date().getTime(), message: new Date().getSeconds() } ]); }; const deleteTodo = (todoID) => { const newTodos = todos.filter((todo) => todo.id !== todoID); setTodos((t) => [...newTodos]); }; return <Todos todos={todos} addTodo={addTodo} deleteTodo={deleteTodo} />; }; const root = ReactDOM.createRoot(document.getElementById("root")); root.render(<App />);
Todos.js
import Todo from "./todo"; const Todos = ({ todos, addTodo, deleteTodo }) => { console.log("nTodoList render"); return ( <> <h2>My Todos</h2> {todos.map((todo, index) => { return ( <Todo key={index} todomessage={todo.message} id={todo.id} deleteTodo={deleteTodo} /> ); })} <button onClick={addTodo}>Add Todo</button> </> ); }; export default Todos;
todo.js
import { useEffect } from "react"; const Todo = ({ todomessage, id, deleteTodo }) => { useEffect(() => { console.log("todo " + todomessage + " rendered"); setTimeout(() => { deleteTodo(id); console.log("todo " + todomessage + " rendered 3s ago"); }, 3000); }); return <p>{todomessage}</p>; }; export default Todo;
Advertisement
Answer
Move the declaration of newTodos
inside the setTodos
callback and use the todos
parameter instead of the todos
variable from the useState
call (since it will end up being stale due to the setTimeout
):
const deleteTodo = (todoID) => { setTodos((todos) => { const newTodos = todos.filter((todo) => todo.id !== todoID); return newTodos }); }