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

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]);
```

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.

react 18 useState and useEffect re-rendering

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.

Why would a value get stale using closure in React?

In the documentation for useEffect() it shows the following example:
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
}
It then says:
Experienced JavaScript developers might notice that the function
passed to useEffect is going to be different on every render. This is
intentional. In fact, this is what lets us read the count value from
inside the effect without worrying about it getting stale. Every time
we re-render, we schedule a different effect, replacing the previous
one.
I don't understand why count would get stale inside useEffect() since useEffect has count within its closure. Since useEffect() is a hook, shouldn't it always have access to the latest state variable? Also, in general, how can I be sure that I always have the latest value for a state variable?
I don't understand why count would get stale inside useEffect() since useEffect has count within its closure.
Keep in mind that Example is going to be called multiple times, once for every render. So there are N renders, N count variables, and N useEffect functions. So it's true that each effect function has count within its closure, but each of them has a specific count within its closure. Whatever value it had when the closure was created, that's the value it will have when the effect runs. If you only created the function once, closing over the first value of count, then that code would only ever see the first value of count (ie, 0).
Also, in general, how can I be sure that I always have the latest value for a state variable?
If you're setting state, then you can always access the latest value by using the function version of setState. For example:
setCount(prev => /* calculate new state from prev */)`
For other cases, either include the state in the dependency array of your useEffect, so that the effect re-runs when the count changes:
useEffect(() => {
// some code that uses `count`
}, [count]);
Or leave the dependency array off entirely if you want the effect to run on every render.
useEffect(() => {
// some code that uses `count`
});

React Functional Component Lifecycle Flow with useEffect, both [] and [var]

My functional component is structured as follows:
// Initialization
useEffect(() => {
fetchDetailedUserInfo();
}, []);
// Update on initialization of detailedUserInfo
useEffect(() => {
if (detailedUserInfo) {
//...
setInitialFormValues(..);
}
}, [detailedUserInfo]);
// Update on initialization of initialFormValues
useEffect(() => {
//...
}, [initialFormValues]);
// Return JSX
return ( .. );
What determines when the render (return) runs in between all these useEffects, and how are the useEffects ordered among themselves? What is the exact flow?
According to docs:
By default, effects run after every completed render, but you can choose to fire them only when certain values have changed.
The clean-up function runs before the component is removed from the UI to prevent memory leaks. Additionally, if a component renders multiple times (as they typically do), the previous effect is cleaned up before executing the next effect. In our example, this means a new subscription is created on every update. To avoid firing an effect on every update, refer to the next section.
A lot of people tend to miss the above statement on the documentation.
There are two behaviours to be noticed.
When there is an empty dependency array []
In this case the cleanup effect will only get called when the component is going to unmount
When there is some dependency in the dep array
In this case, the cleanup effect is called everytime the useEffect is triggered due to the update phase, it will be called first, then the callback function will be called, so as to ensure that any cleanup is done before the next callback is called.
The main reason for this confusion is that when someone comes from class components this is not the case, and they tend to follow the lifecycle diagram provided.
The hooks LC diagram is not really official, and has a small flaw
https://github.com/Wavez/react-hooks-lifecycle/issues/5
I had raised an issue on the github repo as well with the correction.
Its still open though.
Do checkout the docs here
https://reactjs.org/docs/hooks-reference.html#cleaning-up-an-effect
why dont you do something like this instead?
useEffect(() => {
// you can return the promise inside the fetchDetailedUserInfo function, and ensure that you return the reponse. make sure to add a catch block or handle that in the response.
fetchDetailedUserInfo()
.then(res=>{
setInitialFormValues(...)
})
}, []);
The first useEffect runs when the component is first rendered, so will fire first. The second useEffect will fire whenever detailedUserInfo has changed its value (assuming its not null/false, etc.). The third useEffect will fire whenever initialFormValues has changed its value.

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