Skip to content
Advertisement

How to render only new items in a react list and not re render the whole list

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

Gif example of my problem

  • 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 Edit Wawa

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
  });
}
Advertisement