react 18 useState and useEffect re-rendering - reactjs

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.

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

React state change is one step behind

A common problem in React, especially among beginners (like me). This is due to the fact that setState is an asynchronous function and React does not change values one by one, but accumulates some pool if it is not otherwise instructed. (please correct me if the wording is wrong)
So here's the question. One simple solution is to put the desired state into the useEffect dependency array.
For example, I have such a component:
const [first, setFirst] = useState()
const [second, setSecond] = useState()
useEffect(() => {
Something logic by first and second...
}, [first])
Here, the useEffect is executed every time the first state changes. At the same time, at the moment of its operation, the second state is not relevant, next time it will become as it should be now, and so on. Always one step behind.
But if I add a second state to the useEffect dependency array, then everything works as it should.
useEffect(() => {
Something logic by first and second...
}, [first, second])
At the same time, it is not necessary that the useEffect work on changing the second state, I added it only in order to have an up-to-date version inside the useEffect. Can I use the useEffect dependency array this way?
if you use useEffect with an empty array it will act as componentDidMount and componentWillUnmount so it will only invoke itself on first component creation and when it is about to unmount.
https://reactjs.org/docs/react-component.html#componentdidmount
Here I got the problem You are facing, I am not fully sure, but In my case I was console.log(result) withing the fucntion that was changing state, but it was always one step behind. why was that? because in React it is considered as side effect. So If you console.log(result) in useEffect passing the value in dependency array then, it will console log the same value that instantly changed.
In backgorund the state is updating exactly the same time but useEffect detects it as exactly as it is changed.
You can write any logic in useEffect as well or in the function which you are updating.
So there should not be any problem writing logic in the function which you are callilng on click.

In React, if a dependency is changed to the same value, does useEffect get fired?

Suppose we have this:
const [number, set_number] = useState(1);
useEffect(() => {
// do something
}, [number]);
What if some computation did that:
set_number(2 - 1);
Will the useEffect get trigged in this case?
In React, if a dependency is changed to the same value, does useEffect
get fired?
No it does not, the purpose of dependencies is to fire effect when at least one of its dependencies changes (i.e. has different value than it had on previous render). From the docs:
You can tell React to skip applying an effect if certain values
haven’t changed between re-renders. To do so, pass an array as an
optional second argument to useEffect

Dependencies in React useEffect cleanup function are not updated

I am facing a strange issue when trying to call a cleanup function on component unmount with useEffect.
In my useEffect return I call a useCallback function where the dependencies are added correctly. In there a check the state variable called status but this variable never get updated from the initial state. I cannot pass the variable to the useEffect as I want to trigger it only when the component unmounts for specific reasons.
I recreated a simplified version in the codepen here and I can't get my head around this. Maybe someone knows why this is happening?
Thank you!
(this just started happening recently and it was working previously so I'm even more confused!)
Thank you for your answers.
So, I finally found out what happens.
useEffect creates a closure and the function is in that closure, which means that the status, being a string, remains as for the first render (when the closure gets created) and it never gets updated.
A way of giving this is using useRef, as mentioned by #ilkerkaran, but that's because it creates an object, which means that the ref.current property has a link to the original one and it's always in sync.
Another way would be to do useMemo and return an object with the status property, which is practically useRef under the hood.
So practically, if the state were an object and we passed state as a dependency, the stayus property would work as expected for the same reason. I hope this helps also someone else and saves some time
Actually that's not what happens on your code. The callback function is updated according to dependency array. You can see that by calling remove() just above useEffect. That way, the func will be executed on every render.
What happens in your example;
it renders, (by pressing the toggle button for the first time)
then you trigger second render by calling setStatus("mounted") in useEffect (by pressing the toggle button for the second time)
then it renders for te last time for unmount with the default state values
Last part also bugs me actually. You can observe this behaviour by putting a simple console.log just above your useEffect definition.
You also can work around this by using useRef instead of useState
The reason for this is your useEffect .
React.useEffect(() => {
setStatus("mounted")
return () => remove()
}, [])
You have an useEffect with the dependency set to []. This means that your useEffect will run only once . So this is how the flow goes your component is executed from the top to bottom so you create a remove function which at this point of time has your initial state as not mounted . Now your dom get painted. You useEffect gets called you set the state now you get a brand new remove function . Now you unmount your component the clean up will use the remove function from the first render.
In order for your state to reflect in the remove you need to add status as the dependency in the useEffect .
React.useEffect(() => {
setStatus("mounted")
return () => remove()
}, [status])

Make React useEffect only run at initial render and when property changes

I have a useEffect that I want only to be run on the initial render and when a variable changes. That means that I don't want it to be run every time the component rerenders. This is where I'm stuck, this is my current code simplified:
useEffect(() => {
// Some logic only to be performed when variable changes OR at initial render
}, [variable]);
Any ideas on how to achieve this?
You need to conditionally run it on every render, so get rid of the dependency array and add conditions inside your useEffect.
Otherwise, you can make 2 useEffects, one with an empty dependency array, and the other with the state variable.
In case you want your useEffect to run on initial render then do it like this:
useEffect(()=>{
// do whatever you want to do here
},[]);
and in case you want it to run on some variable change then:
useEffect(() => {
// Some logic only to be performed when variable changes OR at initial render
}, [variable]);
```

Update a state variable only on loading the page for the first time in react js

I have a react component and I want to update the state variable to be false when the page loads for the first time. I am using useEffect for that, but later down the page I am using a third party library that calls a function which has a function to update the same state variable to be true and that is what I want but that runs the useEffect again which makes it back to false.
So, I want to make sure the state variable to be only false when we load/refresh the page
useEffect(() => {
Dosomething();
DoAnotherThing();
changeState();
}[Dosomething,DoAnotherThing,changeState]);
return (
<>
<ThirdPartyComponent>
//on Load dispatches another function
AnotherFunction();
</ThirdPartyComponent>
</>
)
Actions
AnotherFunction(){
changeState(); //I want the state change to happen here but it calls the useEffect back again which makes it false
}
So, I am trying to make sure that state variable is false only on load/refresh of the page. I am using Redux for state management
Your useEffect is triggered every time your component is re renderer, if you want it to run only when the component is mounted, you should use useEffect with an empty array as a second argument.
useEffect(() => {//stuff},[])
If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run. This isn’t handled as a special case — it follows directly from how the dependencies array always works.
Here
Pass a blank array as second argument of useEffect.
useEffect(() => {
changeState();
}, []);

Resources