React "interval" has always the same state - reactjs

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

Related

Building a counter using React useEffect hook [duplicate]

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.

useEffect not producing data in console.log in sequence

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

React redux value inside a function of a functional component returns the value a cycle late

I recently changed my code from class components to functional components and using hooks. However, I ran into a problem where the accessing a redux value using useSelector and useDispatch.
let started = useSelector((state: any) => state.routines.started)
const dispatch = useDispatch()
The value that I retrieve from useSelector works, however, when accessing this value inside a function, the value of started only returns its previous expected value. Only after the second rerender that the value of started changes to its expected value.
Say I have this button code
<Button onPress={() => startRoutine()}>Start routine</Button>
And the startRoutine function
let startRoutine = () => {
dispatch(startRoutineAction())
console.log(`started (startRoutine Home): ${started}`);
if(started){
...START CODE
} else {
...STOP CODE
}
}
I expect that after dispatching the startRoutineAction() method, the value of started becomes changes true to false. However, I get false after logging it in the startRoutine function.
The strange part is that I get true when I log it outside the startRoutine function like this. This is still within the function component
console.log(started)
return (
...VIEW CODE
)
I noticed then that although the actions are being dispatched properly, the values accessed from within the startRoutine function are being accessed a cycle late. This can be seen in a counter, where I'm logging the current count after dispatching an addCount() function
let addCount = () => {
dispatch(addCount())
console.log(`count: ${count}`);
}
If the value of count starts at 0. Then the first time the addCount function is run, it will return the count value of 0, even though the expected value should be 1
I'm using the following package versions
expo ^35.0.0
react-redux ^7.1.1
redux-persist ^5.10.0.
In Redux as soon as an Action is dispatched it doesn't update the store immediately it will call the reducer with the action and the current state, it won't wait till the reducer is finished updating the state and in the meantime, your next statement is executed.
So the value does get updated but you get the previous state in your console statement.
You can check this doc.

When exactly do React Hook states get set?

I am trying to understand the order of execution for the following piece of code involving React Hook states:
const App = () => {
const [ searchedCountry, setSearchedCountry ] = useState('');
const [ filteredArr, setFilteredArr ] = useState([]);
const [ filteredLength, setFilteredLength ] = useState(0);
//...
const filterCountries = (event) => {
event.preventDefault();
setFilteredArr(countries.filter(country => country.name.includes(searchedCountry)));
setFilteredLength(filteredArr.length);
console.log("filtered arr length", filtered.length);
}
//...
}
When filterCountries is triggered, setFilteredArr sets the state, filteredArr, to an array filtered by a query. However, when exactly does filteredArr get set?
filteredArr.length returns 0, meaning filteredArr has not been set yet, even after calling setFilteredArr.
At first, I thought by executing setFilteredArr, the component re-renders, causing execution to skip the method calls after setFilteredArr. That would explain why filteredArr.length is 0. However, the console.log is still called, meaning after a component re-renders, the order of execution is actually resumed.
What is going on? When exactly does filteredArr get set?
The thing to remember is: all your state variables are local variables. They only exist for this particular time the component rendered. So on the first render, console.log("filtered arr length", filteredArr.length); is referring to the array that exists on that first render. filteredArr will never be assigned a new array (it can't, it's a const), and unless you mutate the array (which you shouldn't), the length of that array will always be 0.
When you call setFilteredArr, this instructs react to rerender the component. React might do the rendering synchronously, or it might wait to try to batch up changes. When that 2nd render happens, you make a call to useState and get back the new value. This is assigned to a local variable named filteredArr, but this is a completely different variable than the one we had on the first render. The console.log statement in that first render, will have no way to access the variable in the second render. But the second render has access to it, so any logging you do the second time will show the second array.
setState or 'setFilteredLength' is an async operation. After you call it, it will take some time to update the state (as it is async, it will not wait for that update. It will simply execute the next line) so when it executes the console.log the value has not changed -> yet

useState React hook always returning initial value

locationHistory is always an empty array in the following code:
export function LocationHistoryProvider({ history, children }) {
const [locationHistory, setLocationHistory] = useState([])
useEffect(() => history.listen((location, action) => {
console.log('old state:', locationHistory)
const newLocationHistory = locationHistory ? [...locationHistory, location.pathname] : [location.pathname]
setLocationHistory(newLocationHistory)
}), [history])
return <LocationHistoryContext.Provider value={locationHistory}>{children}</LocationHistoryContext.Provider>
}
console.log always logs []. I have tried doing exactly the same thing in a regular react class and it works fine, which leads me to think I am using hooks wrong.
Any advice would be much appreciated.
UPDATE: Removing the second argument to useEffect ([history]) fixes it. But why? The intention is that this effect will not need to be rerun on every rerender. Becuase it shouldn't need to be. I thought that was the way effects worked.
Adding an empty array also breaks it. It seems [locationHistory] must be added as the 2nd argument to useEffect which stops it from breaking (or no 2nd argument at all). But I am confused why this stops it from breaking? history.listen should run any time the location changes. Why does useEffect need to run again every time locationHistory changes, in order to avoid the aforementioned problem?
P.S. Play around with it here: https://codesandbox.io/s/react-router-ur4d3?fontsize=14 (thanks to lissitz for doing most the leg work there)
You're setting up a listener for the history object, right?
Assuming your history object will remain the same (the very same object reference) across multiple render, this is want you should do:
Set up the listener, after 1st render (i.e: after mounting)
Remove the listener, after unmount
For this you could do it like this:
useEffect(()=>{
history.listen(()=>{//DO WHATEVER});
return () => history.unsubscribe(); // PSEUDO CODE. YOU CAN RETURN A FUNCTION TO CANCEL YOUR LISTENER
},[]); // THIS EMPTY ARRAY MAKES SURE YOUR EFFECT WILL ONLY RUN AFTER 1ST RENDER
But if your history object will change on every render, you'll need to:
cancel the last listener (from the previous render) and
set up a new listener every time your history object changes.
useEffect(()=>{
history.listen(()=>{//DO SOMETHING});
return () => history.unsubscribe(); // PSEUDO CODE. IN THIS CASE, YOU SHOULD RETURN A FUNCTION TO CANCEL YOUR LISTENER
},[history]); // THIS ARRAY MAKES SURE YOUR EFFECT WILL RUN AFTER EVERY RENDER WITH A DIFFERENT `history` OBJECT
NOTE: setState functions are guaranteed to be the same instance across every render. So they don't need to be in the dependency array.
But if you want to access the current state inside of your useEffect. You shouldn't use it directly like you did with the locationHistory (you can, but if you do, you'll need to add it to the dependency array and your effect will run every time it changes). To avoid accessing it directly and adding it to the dependency array, you can do it like this, by using the functional form of the setState method.
setLocationHistory((prevState) => {
if (prevState.length > 0) {
// DO WHATEVER
}
return SOMETHING; // I.E.: SOMETHING WILL BE YOUR NEW STATE
});

Resources