Why would a value get stale using closure in React? - reactjs

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

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.

Is this only a syntax difference `useState()`

const [val,setVal] = React.useState(0);
Sometimes I see this in other peoples code
setVal(()=>val);
Does that have any difference from this.
setVal(val);
As the comments point out, half the time this syntax makes no difference. Where it does comes into play is asynchronisity. For example, say you have a setTimeout call in a useEffect and it waits a few seconds and then performs a state update, but while awaiting, the state is updated from somewhere else in the component. If you were to reference the state value in the setTimeout callback it would be a stale value and so if you called setMyState(prevState + 1) you would be off by 1. However if you use setMyState(prevState => prevState + 1) you have accessed the most up-to-date state value.
Also, it is very useful when you don't want to reference the state value in a hook like useEffect. Anything included in a dependency array will cause the hook to update and that can produce chain reactions or fire off a useEffect. when using setMyState(prev => prev + 1) inside the hook, you can avoid referencing the state value itself.

question on code snippet useEffect with fetching state

I am reading this code snipper https://reactjs.org/docs/hooks-faq.html#how-can-i-do-data-fetching-with-hooks linked from react documentation page https://reactjs.org/docs/hooks-faq.html#how-can-i-do-data-fetching-with-hooks
I don't understand this piece of code with the returned function that modifies the ignore variable
useEffect(() => {
let ignore = false;
async function fetchData() {
const result = await axios('https://hn.algolia.com/api/v1/search?query=' + query);
if (!ignore) setData(result.data);
}
fetchData();
return () => { ignore = true; }
}, [query]);
once the function that is passed to useEffect is called a second time, doesn't ignore reset back to false leaving the ignore = true completely useless. The code above does not compute for me. Thanks for explaining.
The ignored variable is in function scope, current effect's ignored will be a different instance with the one in the next effect.
If an effect returns a cleanup function, React invokes it when the component unmounts or before the effect gets re-run due to a dependency change (if query were to change in this case).
The axios fetch call is asynchronous, so it's possible that the component will unmount or a dependency (e.g. query) will have changed before the data request completes.
If that happens, the returned function will have set ignore to true and the fetchData function won't try to call setData on the unmounted component. (Attempting to update state on an unmounted component causes an error.)
From the docs:
When exactly does React clean up an effect? React performs the cleanup when the component unmounts. However, as we learned earlier, effects run for every render and not just once. This is why React also cleans up effects from the previous render before running the effects next time.
Due to the way javascript closures work, the cleanup function can access the ignore variable in the enclosing scope.
When the effect runs again, a new ignore variable gets declared with a new cleanup function that references the new variable.
Your understanding of the functional call and variable assignment is correct, but you forgot the power of the async call :)
useEffect is called whenever it is needed (e.g. componentDidMount, componentDidUpdate by query keyword), so we roughly have
ignore = false
function fetchData is now defined
function fetchData is called
keep the reset of useEffect in your mind for a sec.
let's say something happens which causes a re-render e.g. prop-change (but other than query) or even call that setData with newly fetched data.
your useEffect will affect nothing, cause query shallow-comparison can not find a change with the previous one. So the purpose of the ignore is not involved in this case, cause it is not going to change at all.
Also if the query change in any way the function execution would roughly (ignoring event loop, macro task everything for the moment) be something like:
// ignore is false and for some reason query changes...
(() => { ignore = true })() // ignore = true
// continue running useEffect again.
// lets think nothing else will happend in gap
ignore = false // ignore = false
The only possible scenario I can think about is when your component is going to be unmounted when your useEffect fallback is being called. So here is when that async keyword wants its dignity back 🙌️.
Remember: The async function is not return anything by your command.
Imagine the data arrive when your component is fully removed from the DOM, here is the role which that ignore=true plays in this useEffect.
Preventing setData on unmounted component

understanding async behavior of use Effect hook working

const [count,setCount]=useState(5)
useEffect(()=>{
setCount(10)},[count])
In above code if i am updating state without async operation useEffect doesn't goes to infite loop.While in below code if i update sate through asysnc operation it goes to infite loop can some one tell me the behavior of useEffect in the case of async oeration?
const [count,setCount]=useState(5)
useEffect(()=>{
//some async operation
.then(response)=>{
setCount(response.data)}},[count])
This is because numbers are compared to values and objects by references. Each time async operation result in new object reference, hence useEffect triggers.
In case of number like setCount(5) will not trigger again as second time 5 is the same variable as previous.
Thats a good question!
In the first snippet you were setting the count to 10, that value is not changing dynamically and hence it will change once and wait for the count to change again which will not happen over there, whereas in the second snippet once the async operation is completed it will update the count and because of this count update useEffect will be triggered again along with the async operation in it. So it goes into a recursive updation.
If you want, you can change the first snippet like this to see the recursive updation
setCount(count + 1);
This is the catch of hooks and how they update state, if your previous state value and current state value is same then react functional component won't re-renders, so it is advisable if you are working with object type of state then always create new object or use spread operator for updating any property inside object while updating your state. In your case setCount(10) will always return a constant value of 10 so useEffect won't trigger infinite times as count is theoretically changed once from 5 to 10, but for an API request it will always returns an object with new reference so it'll create an infinite loop. You can also do setCount(10) inside your API result and it also won't trigger useEffect as count is basically never changed from 10 except from a change 5 to 10.

Resources