Here's the issue: I'm trying to call 2 functions on a button click. Both functions update the state (I'm using the useState hook). First function updates value1 correctly to 'new 1', but after 1s (setTimeout) second function fires, and it changes value 2 to 'new 2' BUT! It set's value1 back to '1'. Why is this happening? Thanks in advance!
import React, { useState } from "react"; const Test = () => { const [state, setState] = useState({ value1: "1", value2: "2" }); const changeValue1 = () => { setState({ ...state, value1: "new 1" }); }; const changeValue2 = () => { setState({ ...state, value2: "new 2" }); }; return ( <> <button onClick={() => { changeValue1(); setTimeout(changeValue2, 1000); }} > CHANGE BOTH </button> <h1>{state.value1}</h1> <h1>{state.value2}</h1> </> ); }; export default Test;
To set a conditional initial value for useState in React:Pass a function to the useState hook. Use a condition to determine the correct initial value for the state variable. The function will only be invoked on the initial render.
To update the state, call the state updater function with the new state setState(newState) . Alternatively, if you need to update the state based on the previous state, supply a callback function setState(prevState => newState) .
The answer: They're just queues setState , and React. useState create queues for React core to update the state object of a React component. So the process to update React state is asynchronous for performance reasons. That's why changes don't feel immediate.
Welcome to the closure hell. This issue happens because whenever setState
is called, state
gets a new memory reference, but the functions changeValue1
and changeValue2
, because of closure, keep the old initial state
reference.
A solution to ensure the setState
from changeValue1
and changeValue2
gets the latest state is by using a callback (having the previous state as a parameter):
import React, { useState } from "react"; const Test = () => { const [state, setState] = useState({ value1: "1", value2: "2" }); const changeValue1 = () => { setState((prevState) => ({ ...prevState, value1: "new 1" })); }; const changeValue2 = () => { setState((prevState) => ({ ...prevState, value2: "new 2" })); }; // ... };
You can find a broader discussion about this closure issue here and here.
Your functions should be like this:
const changeValue1 = () => { setState((prevState) => ({ ...prevState, value1: "new 1" })); }; const changeValue2 = () => { setState((prevState) => ({ ...prevState, value2: "new 2" })); };
Thus you make sure you are not missing any existing property in the current state by using the previous state when the action is fired. Also thus you avoid to have to manage closures.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With