react 18 useState and useEffect re-rendering

Viewed 939

Tell me why the re-rendering on the page does not work after the change in userstate Changes occur after clicking on the page again, and not immediately.

  useEffect(() => {
    const sortfdb = Datadb.sort((a,b) => b.count - a.count);
    console.log(sortfdb)
    setDatadb(sortfdb);
    console.log(Datadb)
  },[Datadb])

I have a field on the page, I will add a number, it writes to useState([]), and when changing the useState, it should sort them. But this is not happening. And it happens only when I add a new number.

useEffect, which catches changes, works, and displays everything in the console as it should, but does not want to change it right away on the page It worked fine in react 17, but not now. I can't figure out what went wrong


Update

I realized my mistake, I had to provide sort as an array

setDatadb(el=>[...el].sort((a,b) => b.count - a.count));

But now it throws an error indefinitely, as rendering is constantly happening

next-dev.js?3515:25 Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
4 Answers

Issue

You are mutating the current state with Array.prototype.sort.

The sort() method sorts the elements of an array in place and returns the sorted array.

Additionally, the Datadb state array reference never changes, so React bails on rerendering because it uses shallow reference equality checks as part of the reconciliation process.

useEffect(() => {
  const sortfdb = Datadb.sort((a, b) => b.count - a.count); // <-- mutation
  console.log(sortfdb); // <-- sortfdb reference to Datadb state
  setDatadb(sortfdb);   // <-- state reference never changed, no rerender!
  console.log(Datadb);
}, [Datadb]);

When you fix the array reference issue, the issue then becomes a hook dependency issue.

useEffect(() => {
  // update dependency, render loop!
  setDatadb(el => [...el].sort((a, b) => b.count - a.count));
}, [Datadb]);

Don't unconditionally update a value that is in the hook's dependency array as this will trigger a rerender by React and the hook runs again, enqueues the update and triggers another rerender, repeat ad nauseam. This is the render looping error React warns about.

Solution

If you need to sort the Datadb state in an useEffect then don't use the Datadb state as the hook's dependency. Use an empty dependency array or some other value to "trigger" the effect.

Example:

const [sortData, setSortData] = React.useState(false);

useEffect(() => {
  if (sortData) {
    setDatadb(data => data.slice().sort((a, b) => b.count - a.count));
    setSortData(false);
  }
}, [sortData]);

...

// some event handler
setSortData(true); // to trigger sorting

You might not need the useEffect hook at all if all you want to do is sort the Datadb state in response to an event. You can/should directly enqueue the state update to sort the data in that handler function.

Example:

const sortDataByCount = () => {
  setDatadb(data => data.slice().sort((a, b) => b.count - a.count));
};

...

<button type="button" onClick={sortDataByCount}>
  Sort by Count
</button>

setting state is async, when console.log(Datadb) is executed, setDatadb(sortfdb) is not yet done. This is the expected behavior even before React 18. However, the state will change, you just can't console log it since it is async...

The error was that sort does not provide an array, solved the problem like this:

setDatadb(el=>[...el].sort((a,b) => b.count - a.count));

I removed the check from useEffect, and moved it immediately after adding a new variable.

There is no infinite loop anymore, in useEffect and most importantly, after adding a new variable, it sorts the values

next-dev.js?3515:25 Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.

Updating values to the state should not be done in useEffect. You have to create a function outside and update the state value.

This happens because once if you update a state the reference of the state will be changed and component rerender happens. Again since the reference is changed. The useEffect will be called again and this happens infinitely which the react stops after certain extent.

Related