This question already has answers here:
Why React useState with functional update form is needed?
(5 answers)
Closed 6 months ago.
Can Anyone tell me the difference between this :
function IntervalHookCounter() {
const [count,setCount] = useState(0)
useEffect(()=>{
const interval = setInterval(tick,1000)
},[])
const tick = ()=>{
setCount(count+1)
}
And this :
function IntervalHookCounter() {
const [count,setCount] = useState(0)
useEffect(()=>{
const interval = setInterval(tick,1000)
},[])
const tick = ()=>{
setCount(prevState=>prevState+1)
}
I don't understand why the first one is not woriking.
Because the useEffect call that's run exactly once captures the "instance" of the tick function it sees at the time the component mounts. That "instance" of the tick function will have captured the value of count, which is initially 0.
Thus, that setInterval basically always runs setCount(0 + 1).
The second implementation doesn't do that, as the functional update form of setState has React itself provide the previous value of the state atom to the update function, so there's no "stale" count to have been captured.
Ok, so this question isn't actually about useEffect but about SetStateAction.
Ie:
setState(tick + 1);
vs
setState(tick => tick + 1);
The answer has to do with concurrency. What if you call setState(tick + 1), twice in the same render? You will update it to the same value, instead of incrementing twice like you would expect. If you pass a function to setState you will get as the first argument the latest value, instead of the value that was bound to your render.
Edit:
One issue that your code has:
function IntervalHookCounter() {
const [count,setCount] = useState(0)
useEffect(()=>{
const interval = setInterval(tick,1000)
},[])
const tick = ()=>{
//...
}
Is that you're not managing your dependencies accurately.
Your useEffect has a depdency on tick, and tick has dependency on count. Except in the 2nd example it doesn't have that dependency on count.
If you install a linting tool you will see warnings that you're missing dependencies in your code. And in the first example it's actually a problem. If the first example, adding tick as a dependency will cause the code to behave in unintended ways, especially since you're not cleaning up your setInterval in the useEffect.
This happens when the new state value depends on the previous state. Since the state update operation is a batch operation, the operation without knowing the previous state may lead you to the wrong result. Therefore, if multiple state update calls are made at the same time, the old state value is obtained first and updated over it.
Related
My understanding was that useEffect ran whenever the dependencies in the array were rerendered (or only at the first render in if it's blank) or whenever ANYTHING rerendered if there was no Array.
I also thought the code directly in the function about return (not inside a hook though) i.e. like how you declare variables to hold values of setState, or declared functions, only ran "once" (though i'm not sure when)
However, in the example below, I saw the console.log statement run multuple times, almost in tandem with useEffect. Specifically it seemed that the console.log was running in some weird offsync pattern with useEffect, but I don't understand why it would be running, if my variables aren't being redeclared and such. Like, even if it's on every render, similar to a useEffect with no specified dependencies, wouldn't it then also be reintializing the useState variables and such??
So my questions are
When does the code in a "raw" functional component get run, i.e. the console.log("FLOATING CODE") -> I would either think it only ran on initialization OR it ran every rerender, but neither of these seem to be the case. The question is based on the discrepancy between how the functional code reruns, but the initialization code doesnt.
Also why does it run TWICE
why is the value of "log" different on the web page and in the console.log? The setLog is literally before the console.log(), shouldn't it update FIRST? Esp given that the update clearly goes through for the page text to rerender.
import React, { useState, useEffect } from 'react';
const App = () => {
const [num, setNum] = useState(1);
const [log, setLog] = useState('');
const add = () => {
setNum(num + 1);
};
useEffect(() => {
setLog('useEffect has run-> NUM was rerendered ' + num);
console.log(log)
}, [num]);
// setLog(log + " floating code has run");
console.log('\n FLOATING CODE HAS RUN ' + num);
return (
<>
<button onClick={add}>{num}</button>
<div>{log}</div>
</>
);
};
export default App;
Thanks
Also I saw What is the difference between useEffect and code in the body of a functional component? to try and answer my first question, but it didn't explain about declarations, only about the functional difference.
In response to your questions:
The code in the body of a functional component executes every time the component is rendered (your console.log('\n FLOATING CODE HAS RUN ' + num);) In contrast, code in a useEffect fires on the initial render, and then on every render during which the value of one of the elements in the dependency array has changed. In your example, it should run on the first render, and then every time setNum causes the value of num to change.
The reason for it executing twice is difficult to determine, but it may not be an issue. It is likely being caused by internal React mechanisms such as 'strict mode', and will not occur in the production build.
No, setLog should not necessarily execute before the console.log. React setState functions are not guaranteed to execute in sequential order. They should not be regarded as synchronous. You have to take this into consideration when designing your component. This is why, when setting a state that is based on a previous state, you need to pass a callback to the 'set' function with the previous state as a parameter. With that in mind, your const add = () => { setNum(num + 1); }; line is incorrect. It should instead be const add = () => { setNum(prevNum => prevNum + 1); };
Why does the rendering occur twice in this code?
I tried to remove the <React.StrictMode></React.StrictMode>
but I can't fixed the re-rendering
I don't think it's a "useState" issue
const [memberArray, setMemberArray] = useState<any[]>([]);
const [memberArea, setMemberArea] = useState(0);
console.log("memberArea: " + memberArea);
const getData = async () => {
try {
const res: any = await axios.get(
`api/v1/member?memberCode=${memberNum}`
);
const data: any = res.data.data.memberList;
setMemberArray([...data]);
} catch (err: any) {
console.log(err);
}
};
useEffect(() => {
getData();
}, [memberArea]);
Your effect has one dependecy, memeberArea, meaning it will execute effect each time memberArea changes. Problem with you approach is because your effect depends on something which is updated by effect itself, without any safe condition to avoid infinity loop or unnecessary state updates.
First time element renders when mounted. Then you step into useEffect and update state inside effect, thus forcing additional, second, rerender, because memberArea changed by effect. Problem with your code are not rerenders, but the fact that you will end up with another effect execution because memberArea changed in first call and this will lead you to one extra call of getData, so you end up with 2 rerenders(which is fine) and 2 subsequent api calls(which is wrong because you will update state with same value twice). Special case is your memberArray that, with the current solution, will be updated twice, and since it is array(complex type) you will override reference twice thus forcing jsx part that consumes memberArray to rerender 3 times(initially, on first and on second api call).
If you want to call getData only once and prevent this side effect you should remove dependency and leave empty array, thus forcing effect to be executed only once, otherwise you would need to insert some checks inside effect to prevent unnecessary state updates like you do now.
i am a begginer to react i am learning the useEffect hook currently i am a little bit confused over the sequence at which the data in console.log is printed , please explain it in steps , thanks
initailly i see b and c printed but then i see a ,b ,c after each second why is that ?
code
const [count, setCount] = useState(0);
const tick = () => {
setCount(count + 1)
}
useEffect(() => {
console.log("b")
const intervel = setInterval(tick, 1000)
console.log("c")
return () => {
console.log("a")
clearInterval(intervel)
}
}, [count]);
The function inside which you print a is called a cleanup function.
React calls that function before applying the next effects. That is why you see afterwards a printed each time count is changed. It is a cleanup being called from the previous render before applying effect for this render. 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 render1 and not just once. This is why React also cleans
up effects from the previous render before running the effects next
time. We’ll discuss why this helps avoid bugs and how to opt out of
this behavior in case it creates performance issues later below.
1 That part of docs didn't talk about dependencies yet, hence it mentions effects running on each render.
The function you are returning at the end of useEffect()
return () => {
console.log("a")
clearInterval(intervel)
}
is not run the first time the component renders. That is called a cleanup function. You are returning that anonymous function to the top of the execution queue on the next render.
Knowing that, we can see that your render cycle will look something like this
Render 1:
console.log("b")
wait 1 second
console.log("c")
return console.log("a") to next render
Render 2:
console.log("a")
console.log("b")
wait 1 second
console.log("c")
return console.log("a") to next render
you set that useEffect depend on [count]
it means that you ask useEffect to recall everytime count change and call returned function (
return ()=>{
console.log("a")
clearInterval(intervel)
}
) after count change so
first component render
after render useEffect call this function
console.log("b")
const intervel = setInterval(tick, 1000)
console.log("c")
but tick change count every 1s and useEffect depend on it so after 1s
useEffect will clear because count changed so interval will stop
and will call again
so it will create a new interval and start again again again
I have a paginated list of data that I am updating based on a filter and an offset (page)
When the offset is updated (next/prev page) I hit the API and get new data. When the filter is updated I reset the offset to 0.
The problem is, when the filter is updated and the offset is updated it causes the useEffect to be fired twice. Which in turn calls the api twice.
const [filter, setFilter] = useState('All');
const [offset, setOffset] = useState(0);
onFilterChange = (value) => {
setFilter(value);
offset !== 0 && setOffset(0);
}
getDataFromAPI = useCallback(() => {
const endpoint = `https://example.com/data?filter=${filter}&offset=${offset}`;
CallApi(endpoint);
}, [offset, filter])
useEffect(getDataFromAPI, [getDataFromAPI]);
I think the fix would be to get rid of useEffect in such case. Sometimes it is used needlessly. Just call CallApi inside the onFilterChange and onOffsetChange handlers with the new values that were set, and that's it.
Here are some relevant quotes from Dan Abramov:
In hindsight, my personal conviction has become that if some effect
can’t safely over-fire (e.g. fire twice instead of once), there is a
problem. Usually things that can’t over-fire are related to user
actions ("buy", "send", "finish"). Actions start in event handlers.
Use them!
To sum up, if something happens because a user did something,
useEffect might not be the best tool.
On the other hand, if an effect merely synchronizes something (Google
Map coordinates on a widget) to the current state, useEffect is a good
tool. And it can safely over-fire.
PS But just to note I thought in your case react would batch the two different set state calls inside the filter change handler (hence call render once), but it seems it doesn't? In any case you may still prefer the recommendation in the beginning of my answer to remove useEffect.
I am using react 16.10 with typescript.
I have this code:
const [state, setState] = useState<State>({
test: 1
});
//On component mount we start our interval
useEffect(() => {
const timerID = setInterval(timer, 5000); //every 5 seconds
return function cleanup() {
//When we leave component we stop the timer
clearInterval(timerID);
};
}, []);
function timer() {
if(state.test === 3){
//HE WILL NEVER ENTER THIS CODE FUNCTION
}
setState({...state, test: 3}); // Next time we should have the value 3, BUT IT HAS NEVER THIS VALUE?!?!
}
return (
<>
<span>The output is {state.test}</span> //First it is showing 1, after 5 seconds 3. Working great
</>
);
I am changing the value of test to the number 3 in the interval "timer". setState is working fine. I can see the value in my component, seeing the number switching from 1 to 3.
But in the timer function the value is never changed. It has every time the default value of 1.
What I am doing wrong?
You need to add dependency to useEffect
//On component mount we start our interval
useEffect(() => {
const timerID = setInterval(timer, 5000); //every 5 seconds
return function cleanup() {
//When we leave component we stop the timer
clearInterval(timerID);
};
}, [state.test]); // <- here add dependency
Reason
Your effect function is called only once when component is mounted and it stored timer functions reference. now when you state changes your timer function is also updated outside but not inside of useEffect.
useEffect still uses old reference when state was 1 so inside it State always going to be 1 for that referred timer function
Now when you pass state.test as dependency. when state get changed your effect will updated and it now start using new timer function which has new state.
So now, you can have updated state in your timer function. and your condition can evaluate correctly.
if any doubts please comment.
You are not doing anything wrong, your useEffect() has a completely different value in memory and without knowing this behavior about useEffect() you have nothing in there telling useEffect() to stop looking at that old value and start looking at the new value. As Hardik wrote, your useEffect() is called only once, so you still have that old value that was originally called in there and useEffect has no idea that your timer has changed since. It will be referencing that original value forever.
What you can do is completely remove the empty array as the second argument and you will notice the difference in behavior.
Using a direct reference to the variable you are using in your state as suggested by Hardik seems to be the way to go.
So again, useEffect() is not being called a second time and as a result, nothing inside it is being ran again so it all in stale reference.
One of the tips the facebook team gives to mitigate this bug:
When you have a useEffect function that references a state, props, or context values, add them to your dependency list. In other words, if you have a props called trackId, you would want to do something like this:
useEffect(() => {
trackId
}, [trackId]);
I see a couple of potential issues, first of all you need to be calling this.state and this.setState. I'd guess state is undefined but this.state won't be. You also don't need to spread your state in your setState function, this.setState({ test: 3}); is good enough, the setState function does this for you.
Secondly you need to update state for every change, it looks like you're only updating if the test value is 3, I'm surprised it's ever 3 with this implementation