The problem that the useState asynchronous callback in React Hooks cannot get the latest value

The useState asynchronous callback in ReactHook cannot get the latest value and its solution

Understand the two parameter passing methods of setState in advance

1. Directly pass in the new value setState(options);

For example:

const [state, setState] = useState(0);

setState(state + 1);

2. Pass in the callback function setState(callBack);

For example:

const [state, setState] = useState(0);

setState((prevState) => prevState + 1); // prevState is to change the previous state value, the value returned by return will overwrite the state value as the new state

UseState asynchronous callback cannot get the latest value and solution

Usually setState can directly use the first method above to pass parameters, but in some special cases, the first method will cause exceptions;

For example, if you want to get the latest status and set the status in an asynchronous callback or closure, the status obtained by the first method is not real-time. React official documentation mentions: any function inside the component, including event processing functions and Effect , are "seen" from the rendering when it was created, so the referenced value is still old, which finally causes an exception in setState:

import React, { useState, useEffect } from 'react';

const App = () => {

const [arr, setArr] = useState([0]);

useEffect(() => {

console.log(arr);

}, [arr]);

const handleClick = () => {

Promise.resolve().then(() => {

setArr([...arr, 1]); // arr before assignment is: [0]

})

.then(() => {

setArr([...arr, 2]); // At this time, before assignment, arr is still in the old state: [0]

});

}

return (

<>

<button onClick={handleClick}>change</button>

</>

);

}

export default App;

In the above code, the App component is actually a closure function. handleClick refers to arr. The value of arr is indeed updated after the first setArr. We can also see in the screenshot below, but the handleClick executed this time The scope of the event processing function is still old, and the arr referenced in it is still old, resulting in [0, 2] after the second setArr:

In the class component, we can use setState(options, callBack); to perform setState again in the second parameter callback function of setState. There is no closure scope problem, but useState in React Hook removes setState. The second parameter, and it's not good if you nest too much;

Solution 1 (recommended):

Use the second (callback) method to pass parameters to the above code

const handleClick = () => {

Promise.resolve().then(() => {

setArr(prevState => [...prevState, 1]); // You can also leave it unchanged here, use the first parameter passing method setArr([...arr, 1]); because there is no need here Get latest status

})

.then(() => {

setArr(prevState => [...prevState, 2]); // This must be changed to the callback function parameter passing method, otherwise the old state will be read, resulting in an exception

});

}

Solution 2:

Use useReducer to imitate forceUpdate in class components to implement forced rendering of components;

import React, { useState, useReducer } from 'react';

const App = () => {

const [arr, setArr] = useState([0]);

const [, forceUpdate] = useReducer(x => x + 1, 0);

const handleClick = () => {

Promise.resolve().then(() => {

arr.push(1); // If you also need to do a rendering here, just call forceUpdate() after changing the state})

.then(() => {

arr.push(2);

forceUpdate();

});

}

return (

<>

<h1>{arr.toString()}</h1>

<button onClick={handleClick}>change</button>

</>

);

}

export default App;

Before clicking:

After clicking:

Solution 3:

Use ref

import React, { useState, useRef, useEffect } from 'react';

const App = () => {

const [arr, setArr] = useState([0]);

let ref = useRef();

useEffect(() => {

ref.current = arr;

console.log(arr);

});

const handleClick = () => {

Promise.resolve().then(() => {

const now = [...ref.current, 1];

ref.current = now;

setArr(now);

})

.then(() => {

setArr([...ref.current, 2]);

});

}

return (

<>

<h1>{arr.toString()}</h1>

<button onClick={handleClick}>change</button>

</>

);

}

export default App;

Guess you like

Origin blog.csdn.net/hyupeng1006/article/details/130968294
Recommended