Home > database >  Cannot use updated state react context
Cannot use updated state react context

Time:12-04

I'm making a simple to-do app with add and delete functionality and I want to implement an undo-delete option.

So far, I've tried using context but I have a problem with context state logic: undo function uses not-yet updated state values, which leads to errors.

The problem is better documented in the demo:

Edit damp-frost-x48zh6

CodePudding user response:

The problem is when you update your states values and then run your setTimeout, the previous values are used in setTimeout not the new ones.

Because setTimeout uses the values at the time that it is created. Read more here.

So you have 2 options to fix your issue.

  1. Pass the new state values to undo function:
const undo = (incomingDeletedTasks, incomingTasks, incomingTimeoutIds) => {
    const newDeletedTasks = [...incomingDeletedTasks];

    // Remove latest deleted task from deletedTasks state
    const latestDeletedTask = newDeletedTasks.pop();
    setDeletedTasks(newDeletedTasks);

    // Remove latest timeout id from timeoutIds state
    const newTimeotIds = [...incomingTimeoutIds];
    newTimeotIds.pop();
    setTimeoutIds(newTimeotIds);

    // Insert latest deleted task into tasks state
    const newTasks = [...incomingTasks];
    newTasks.splice(latestDeletedTask.index, 0, latestDeletedTask);
    setTasks(newTasks);
};

const deleteTask = (id) => {
    const newTasks = [...tasks];

    // Remove task from local tasks state
    let toDelete = newTasks.find((t) => t.id === id);
    const toDeleteIndex = newTasks.indexOf(toDelete);

    newTasks.splice(toDeleteIndex, 1);
    setTasks(newTasks);

    let newDeletedTasks = [...deletedTasks];
    // Push task to deletedTasks state
    if (!deletedTasks.includes(toDelete)) {
      newDeletedTasks = [
        ...newDeletedTasks,
        { ...toDelete, index: toDeleteIndex }
      ];
      setDeletedTasks(newDeletedTasks);
    }

    // Deletes the task after 10 seconds, giving
    // some time for the user to undo the operation
    const currTimeoutId = setTimeout(() => {
      // API call to delete task from db
    }, 10000);
    // Push timeout id to timeotIds state
    const newTimeoutIds = [...timeoutIds, currTimeoutId];
    setTimeoutIds(newTimeoutIds );

    notifyDelete("Task Deleted", () => undo(newDeletedTasks, newTasks, newTimeoutIds));
};
  1. Do it with useRef as mentioned here.

CodePudding user response:

As Amirhossein mentioned, setTimeOut uses only the values which were declared before it was called, and as Toastify is called (which I expect uses setTimeout), the state of deletedTasks hasnt been updated yet.

I also found your code a bit too complex and Im not an expert in useContext, so I simplified it.

One thing we can do is abuse the fact that state of tasks hasnt been updated either while toastify popup is running. Just setTasks(tasks) in the undo function:

const TaskProvider = () => {
  const [tasks, setTasks] = useState([]);

  useEffect(() => {
    setTasks(apiTasks);
  }, []);

  const undo = () => {
    setTasks(tasks);
  };

  const deleteTask = (id) => {
    const newTasks = [...tasks];
    let taskToDelete = newTasks.findIndex((t) => t.id === id);

    notifyDelete("Task Deleted", undo);
    newTasks.splice(taskToDelete, 1);
    setTasks(newTasks);
  };

  return (
    <>
      <TaskList tasks={tasks} deleteTask={deleteTask} />
      <ToastContainer limit={1} position="top-right" closeOnClick={false} />
    </>
  );
};

I am not sure why the 'updated' state (where a task has been deleted) is rendered in UI, although the state itself hasnt changed. I am sure this code is not ideal, and not very semantic either, but for now its all I got lol.

I have to admit the state management in React is sometimes one of the weirdest things for me as a junior dev. Luckily the project Im in uses redux, which I find very convenient in react applications.

https://codesandbox.io/s/solitary-cache-13zmk6?file=/src/TaskProvider.js

  • Related