Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

useState hook setter incorrectly overwrites state

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; 
like image 243
Bartek Avatar asked Oct 01 '19 22:10

Bartek


People also ask

How do I change my initial state of useState?

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.

How do I change state with useState Hook?

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) .

Why does the React useState Hook not update immediately?

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.


2 Answers

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.

like image 188
Alberto Trindade Tavares Avatar answered Sep 25 '22 12:09

Alberto Trindade Tavares


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.

like image 35
Dez Avatar answered Sep 25 '22 12:09

Dez