I am very new to react and trying to learn basic things. While writing some test programs, I ran into a problem: Basically I wanted to add new object to array after every 2 seconds and I am doing it with the help of useEffect and setInterval. I followed the rule of react to not to mutate the state variable, so I created a new variable from old state and set that variable to state setter function. Still I found, it was not giving me updated state.
const [liveChats, setLiveChats] = useState([]);
useEffect(() => {
const interval = setInterval(() => {
const newLiveChats = [...liveChats];
newLiveChats.push({
name: generateRandomName(),
message: generateRandomMessage(20),
});
setLiveChats(newLiveChats);
}, 2000);
return () => clearInterval(interval);
}, []);
I managed to get the updated state with state updater function (as shown below), but I would like to understand what was wrong with old code. What's the rule of thumb here.
const [liveChats, setLiveChats] = useState([]);
useEffect(() => {
const interval = setInterval(() => {
setLiveChats((prevState) => [
{
name: generateRandomName(),
message: generateRandomMessage(20),
},
...prevState,
]);
}, 2000);
return () => clearInterval(interval);
}, []);
Your useEffect
dependency array determines when it will re-run.
In your first snippet, your dependency array is empty, which means liveChats
will only ever been the initial value of liveChats
, which is an empty array.
So even if you push a new object to newLiveChats
and update liveChats
, on the next interval liveChats
will not persist the newly pushed object.
Your second snippet works successfully because the functional update of useState
lets you pass a function that receives the previous state as an argument, which ensures you're always adding onto the latest version of liveChats
.
For illustration, you could modify your first code snippet like so:
const [liveChats, setLiveChats] = useState([]);
useEffect(() => {
const interval = setInterval(() => {
setLiveChats([
...liveChats,
{
name: generateRandomName(),
message: generateRandomMessage(),
}
]);
}, 2000);
return () => clearInterval(interval);
}, [liveChats]); // Now it will re-run every time liveChats updates
In this example, I've added liveChats
to the useEffect
dependency array, so it will re-trigger it every time liveChats
updates.
As a result, liveChats
in the interval function will be the latest value.
Without the interval, however, this would be a bad practice because it would cause an infinite loop.