Установщик ловушки useState неправильно перезаписывает состояние
Здесь проблема: я пытаюсь вызвать 2 функции одним нажатием кнопки. Обе функции обновляют состояние (я использую хук useState). Первая функция корректно обновляет значение 1 до "нового 1", но через 1 с (setTimeout) запускается вторая функция, и она меняет значение 2 до "нового 2", НО! Он устанавливает значение 1 обратно в "1". Почему это происходит?
Заранее спасибо!
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;
Ответы
Ответ 1
Добро пожаловать в аду закрытия. Эта проблема возникает из-за того, что всякий раз, когда вызывается setState
, state
получает новую ссылку на память, но функции changeValue1
и changeValue2
из-за закрытия сохраняют старую начальную ссылку state
.
Решение, обеспечивающее получение setState
из changeValue1
и changeValue2
самого последнего состояния, заключается в использовании обратного вызова (имеющего свежее текущее состояние в качестве параметра):
import React, { useState } from "react";
const Test = () => {
const [state, setState] = useState({
value1: "1",
value2: "2"
});
const changeValue1 = () => {
setState((state) => ({ ...state, value1: "new 1" }));
};
const changeValue2 = () => {
setState((state) => ({ ...state, value2: "new 2" }));
};
// ...
};
Вы можете найти более широкое обсуждение этой проблемы закрытия здесь и здесь.
Ответ 2
Ваши функции должны быть такими:
const changeValue1 = () => {
setState((prevState) => ({ ...prevState, value1: "new 1" }));
};
const changeValue2 = () => {
setState((prevState) => ({ ...prevState, value2: "new 2" }));
};
Таким образом, вы гарантируете, что не пропускаете ни одно существующее свойство в текущем состоянии, используя предыдущее состояние при запуске действия. Также, таким образом, вы избегаете необходимости управлять замыканиями.
Ответ 3
Когда вызывается changeValue2
, сохраняется исходное состояние, поэтому состояние возвращается в исходное состояние, а затем записывается свойство value2
.
В следующий раз, когда после этого вызывается changeValue2
, он удерживает состояние {value1: "1", value2: "new 2"}
, поэтому свойство value1
перезаписывается.
Вам нужна функция стрелки для параметра setState
.
const Test = () => {
const [state, setState] = React.useState({
value1: "1",
value2: "2"
});
const changeValue1 = () => {
setState(prev => ({ ...prev, value1: "new 1" }));
};
const changeValue2 = () => {
setState(prev => ({ ...prev, value2: "new 2" }));
};
return (
<React.Fragment>
<button
onClick={() => {
changeValue1();
setTimeout(changeValue2, 1000);
}}
>
CHANGE BOTH
</button>
<h1>{state.value1}</h1>
<h1>{state.value2}</h1>
</React.Fragment>
);
};
ReactDOM.render(<Test />, document.getElementById('root'));
<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="root"></div>
Ответ 4
Дело в том, что и changeValue1
, и changeValue2
видят состояние от рендеринга, который они были созданы в, поэтому, когда ваш компонент рендерит в первый раз, эти две функции видят:
state= {
value1: "1",
value2: "2"
}
Когда вы нажимаете на кнопку, сначала вызывается changeValue1
, и состояние меняется на {value1: "new1", value2: "2"}
, как и ожидалось.
Теперь через 1 секунду вызывается changeValue2
, но эта функция по-прежнему видит начальное состояние ({value1; "1", value2: "2"}
), поэтому, когда эта функция обновляет состояние следующим образом:
setState({ ...state, value2: "new 2" });
в итоге вы видите: {value1; "1", value2: "new2"}
.
источник