Why does a count using React Hooks useState() alternate between two numbers? - reactjs

The following is a React Hooks experiment of using useState(). It works fine except when the + button was clicked on, then the number can be alternating from 7001 and 7000 and then flashing between some numbers quickly.
Actually, without clicking on the +, the number behaved well but up to about 8000 or 9000, then it might start to flash between some numbers. Why is that and how can it be fixed?
P.S. initial debugging finding was that: it seems Counter() was called multiple times, setting up an Interval Timer every time. So "magically", it seems the useState() ran only once -- for some unknown and magical reason -- or maybe it ran more than once but just returned the exact same content each time, for some magical mechanism. The initial value of 0 really was so for the first time. When it was useState(0) for future times, the count was not 0... we wouldn't want that, but then it wasn't that functional (as in a math function) either.
function Counter() {
const [count, setCount] = React.useState(0);
setInterval(() => {
setCount(count + 1000);
}, 1000);
return (
<div>
<button onClick={() => setCount(count + 1)}> + </button>
{ count }
<button onClick={() => setCount(count - 1)}> - </button>
</div>
);
}
ReactDOM.render(<Counter />, document.querySelector("#root"));
button { margin: 0 1em }
<script src="https://unpkg.com/react#16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js" crossorigin></script>
<div id="root"></div>

Code in functional component gets executed every time when component is re-rendered. So, on each re-render you are starting an infinite timer that adds 1000 to counter each second
Each time you change component's state, React re-renders it. Meaning, every execution of setCount leads to new re-render and new timer is started
Also, setCount is asynchronous and if you need to rely on previous state to determine next one, you should call with callback, it like demonstrated in other answer (setCount(c => c + 1))
Something like this is supposed to work:
import React, {useState, useRef, useEffect} from 'react';
function Counter() {
const [count, setCount] = useState(0);
//useRef gives us an object to store things between re-renders
const timer = useRef();
useEffect(() => {
timer.current = setInterval(() => {
setCount(count => count + 1000);
}, 1000);
//If we return a function, it will be called when component is dismounted
return () => {
clearInterval(timer.current);
}
}, []);
return (
<div>
<button onClick={() => setCount(count => count + 1)}> + </button>
{ count }
<button onClick={() => setCount(count => count - 1)}> - </button>
</div>
);
}

Not quite sure about the 'why is that' but it's fixed with substituting setCount(c => c + 1) in the buttons and setCount(c => c + 1000) in the interval.
Putting the 'setInterval' in an effect also makes sure that there is only one interval...
React.useEffect(() => {
setInterval(() => {
setCount(c => c + 1000);
}, 1000);
},[])
PS Counter() gets called on every render, I think... while useState only gets called once per mounting by design.

Related

understanding useCallback and setState

I have some simple lines of code to test the useCallback and setState hook. First I try to write like this:
const App = () => {
const [count, setCount] = useState(0)
const increase = useCallback(() => {
setCount(count + 1) // Passing value to change state
}, [])
console.log(count)
return (
<div id="main">
<h1>Count: {count}</h1>
<button onClick={increase}>Increase</button>
</div>
)
}
export default App;
and then, passing the callback to setCount instead like this:
const App = () => {
const [count, setCount] = useState(0)
const increase = useCallback(() => {
setCount(count => count + 1) // Passing callback
}, [])
console.log(count)
return (
<div id="main">
<h1>Count: {count}</h1>
<button onClick={increase}>Increase</button>
</div>
)
}
export default App;
I know it is weird to use useCallback in this situation, just for some testing. The question is that why I pass the callback to setCount, the Increase button work correctly, whereas the first one will just re-render on the first click
In the first case useCallback closes over the count and at that time count was 0 so It always changes value to 1
So to make it working you should add count dependency to useCallback as:
CODESANDBOX LINK
const increase = useCallback(() => {
setCount(count + 1); // Passing value to change state
}, [count]);
In the second case it is not taking the value from the closure instead it is taking the current value and adds one to it and set the value.
ADDITIONAL INFO
You can use react-hooks/exhaustive-deps eslint rule to know which dependency is missing and tell you how to correct it.
useCallback() hasn't been used correctly here: the dependencies array should have count in it. So:
const increase = useCallback(() => {
setCount(count + 1) // Passing value to change state
}, [count])
With an empty array as a dependency, it will be memoized for the lifetime of the component and will not update the current count value.
Source: https://reactjs.org/docs/hooks-reference.html#usecallback
Code sandbox
On a side note, using useCallback() is "overoptimizing" in this case, we are complicating the code while the performance impact to the end users is not noticeable. I would only use it to prevent rerenders in complex views e.g. in a huge list.

useDispatch callback uses stale state when triggered multiple times [duplicate]

Normally when we need to update a state in a functional component, we do something like this:
function Example() {
const [count, setCount] = React.useState(0);
return (<div><p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>);
}
When and why will we ever need to use the functional update form?
function Example() {
const [count, setCount] = React.useState(0);
return (<div><p>You clicked {count} times</p>
<button onClick={() => setCount(c=>c + 1)}>
Click me
</button>
</div>);
}
Use the function form when the setter may close over an old state value.
For example, if an async request is initiated, and you want to update state after that's done, the request that was made will have scope of the state as it was at the beginning of the request, which may not be the same as the most up-to-date render state.
You may also need to use the function form if the same state value was just updated, eg
setValue(value + 1);
// complicated logic here
if (someCondition) {
setValue(value => value + 1);
}
because the second call of setValue closes over an old value.
State Updates May Be Asynchronous:
https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
useState is the same as setState in this condition.
You can see the different when call set state twice:
<button
onClick={() => {
setCount(count + 1);
setCount(count + 1);
}}
></button>;
<button
onClick={() => {
setCount(c => (c + 1));
setCount(c => (c + 1));
}}
></button>;
There are other use cases too. For example, when you call useState inside an effect. If new state is dependent on old state, this might cause an infinite loop.
useEffect(() => {
setCounter(counter + 1);
}, [counter]);
You can avoid this by using functional updates:
useEffect(() => {
setCounter(old => old + 1);
}, []);
According to this: https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
The the functional update form make sure that the previous state that you take reference from is the latest / finalized version when there might be multiple setState hook (which is asynchronous) called (for example, if the user spam click on the button)
And also due to its async nature, the state will not be updated right away within a function, for e.g:
func() {
console.log(counter) // counter = 1
setCounter(counter => counter + 1) // counter = 1
console.log(counter) // counter = 1
}
The functional update form also allows the update function to be passed to its children while still having access to the parent’s state.
function MyButton(props){
// return <button onClick={()=>props.onClick(count+1)}>+1</button>; // error as count is not exposed here
return <button onClick={()=>props.onClick(n=>(n+1))}>+1</button>;
}
function Example() {
const [count, setCount] = React.useState(0);
return (<div><p>Counter: {count}</p>
<MyButton onClick={setCount}/>
</div>);
}
ReactDOM.render(<Example/>,document.querySelector("div"));

useEffect() vs setTimeout() for side effects [duplicate]

This question already has answers here:
React - useState - why setTimeout function does not have latest state value?
(2 answers)
Closed 2 years ago.
I have a general question about useEffect() vs setTimeout().
import { useState, useEffect } from 'react';
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => console.log(count)); // effect
const handleClick = () => {
setCount(count + 1);
setTimeout(() => console.log(count), 5000); // log to console after 5 seconds
}
return (
<>
<p>You clicked {count} times</p>
<button onClick={handleClick}>Click Me</button>
</>
);
}
export default Counter;
In the above code,
setCount(count + 1);
setTimeout(() => console.log(count), 5000);
using setTimeout(), I am trying to log count after 5 seconds, by which time setCount(), although asynchronous, would have definitely finished.
Then why does that still print the previous value of count, while useEffect() code prints the updated value? Can someone tell me what I am missing.
A lot of things to say about this code:
import { useState, useEffect } from 'react';
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => console.log(count), [count]); // Add the dependencies array, the callback of use effect will be call only if a dependency is updated
const handleClick = () => {
setCount((count) => count + 1); // Use a callback which takes as argument the previous state, because if you click multiple times on the button, as setCount is asynchronous, it won't add 1 for each click
setTimeout(() => console.log(count), 5000); // log to console after 5 seconds
}
return (
<>
<p>You clicked {count} times</p>
<button onClick={handleClick}>Click Me</button>
</>
);
}
export default Counter;
I think that it is because the callback of setTimeout is evaluated before the njew count variable is computed, so it logs the previous value, where useEffect's callback is called when a dependency is updated, so when you increment count.

what's the difference between setCount(prev => prev + 1) and setCount(count + 1)?

I am reading an example of React hooks. I find they write
setCount(count + 1)
But I usually wrote like this
setCount(prev => prev + 1)
Is there any difference? Which one is better?
There is a difference, in the first, the count will be based on the current value at the time that that render occurred due to the closure in the function.
The second would always use the latest value for the increment.
Because closures are a complicated topic, here's some examples. The first shows the main difference between the two.
The second example shows several ways that will allow things to work properly with closures and effects/hooks
const { useState, useEffect } = React;
function Example(){
const [count1, setCount1] = useState(0);
useEffect(()=>{
setCount1(count1 + 1);
setCount1(count1 + 1);
setCount1(count1 + 1);
},[])
const [count2, setCount2] = useState(0);
useEffect(()=>{
setCount2(prev=> prev + 1);
setCount2(prev=> prev + 1);
setCount2(prev=> prev + 1);
},[])
return <div>
Both count1 and count2 have had 3 increments.
<br/>
count1 stays at 1 because the count1 variable in the useEffect isn't change due to the closure in the arrow function in the useEffect
<br/>
Current count1: {count1}
<br/>
Current count2: {count2}
</div>
}
ReactDOM.render(<Example/>,document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"/>
const { useState, useEffect, useRef } = React;
function Example(){
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const [count3, setCount3] = useState(0);
const count3Ref = useRef(count3);
count3Ref.current = count3;
useEffect(()=>{
const id = setInterval(()=>{
setCount1(count1+1);
setCount2(prev=>prev+1);
setCount3(count3Ref.current+1);
},300)
return ()=>{clearInterval(id)}
},[])
const [count4, setCount4] = useState(0);
useEffect(()=>{
const id = setTimeout(()=>{
setCount4(count4+1);
},300)
return ()=>{clearTimeout(id)}
},[count4])
return <div>
All of the counts theoretically increment every 300ms
<br/>
<br/>
count1 stays at 1 because the count1 variable in the useEffect isn't change due to the closure in the arrow function in the useEffect
<br/>
Current count1: {count1}
<hr/>
count2 uses the functional version of setCount2 so it always uses the latest version and will update properly
<br/>
Current count2: {count2}
<hr/>
count3 increments because refs are mutable by nature and allow us to bypass the closure.
<br/>
Current count3: {count3}
<hr/>
Another possiblity: count4 increments because we properly use the dependency array and force the useEffect to re-run every time count4 changes.
<br/>
Current count4: {count4}
</div>
}
ReactDOM.render(<Example/>,document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"/>
Well, It depends on the situation.
If you want to update the state whose values depend on the previous state then you should use below.
setCount(prev => prev + 1);
This will update the state to the new state.
If you use this like below, then it will update the count for one but not two times because setState is asynchronous in nature.
setCount(count + 1);
setCount(count + 1);
But if you do this
setCount(count => count + 1);
setCount(count => count + 1);
Then it will update the state two times as we are updating the state from the previous state.
const {useState} = React;
const Example = () => {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count => count + 1);
setCount(count => count + 1);
}
return (
<div>
<p> {count} </p>
<button onClick = {handleClick}> Add by 2 </button>
</div>
);
};
ReactDOM.render(
<Example />, document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
I copied note of React Hook
Unlike the setState method found in class components, useState does not automatically merge update objects. You can replicate this behavior by combining the function updater form with object spread syntax:
setState(prevState => {
// Object.assign would also work
return {...prevState, ...updatedValues};
});
Another option is useReducer, which is more suited for managing state objects that contain multiple sub-values.
In most cases they are doing the same job.
But second one is better when your react component becomes super complex. Because count in first case is not reliable if you have many setState() queuing up. It may be some value you don't expect. setCount(prev => prev + 1) always add 1 to its previous value which is more reliable.
Actually you can pass (previousValue, props) as parameters to setState().
setState() is an asynchronous operation. React batches state changes to reduce the number of re-renders. (especially so in React 18 with automatic batching)
State may not change immediately after setState() is called.
Therefore the preferred method is to update state with prevState instead of relying on current state.

Handling out of date state in functional components

I am running into issues setting a state created with the 'useState' hook from within async functions.
I've created a codepen to demonstrate: https://codepen.io/james-ohalloran/pen/ZdNwWQ
const Counter = () => {
const [count, setCount] = useState(0);
const increase = () => {
setTimeout(() => {
setCount(count + 1);
},1000);
}
const decrease = () => {
setTimeout(() => {
setCount(count - 1);
},1000)
};
return (
<div className="wrapper">
<button onClick={decrease}>-</button>
<span className="count">{count}</span>
<button onClick={increase}>+</button>
</div>
);
};
In the above example, if you click 'increase' followed by 'decrease'..you will end up with -1 (I would expect it to be 0).
If this was a React class instead of a functional component, I would assume the solution would be to use bind(this) on the function, but I didn't expect this to be an issue with arrow functions.
It is because of using setTimeout
Let's assume that you've called the increase() 10 times in a second.
count will be always 0. Because the state is updated after a second, every increment() called in a second will have an unupdated count.
So every increment() will call setCount(0 + 1);.
So no matter how many times you call in a second, the count is always 1.
Ah, I found a solution. I didn't realize I'm able to reference the previousState from the useState setter function: https://reactjs.org/docs/hooks-reference.html#functional-updates

Resources