useState невірно налаштовує стан налаштування гака


31

Ось проблема: я намагаюся викликати дві функції при натисканні кнопки. Обидві функції оновлюють стан (я використовую гачок useState). Перша функція правильно поновлює value1 на 'new 1', але після 1s (setTimeout) друга функція запускається, і вона змінює значення 2 на 'new 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;

ви могли б увійти у стан на початку changeValue2?
DanStarns

1
Я настійно рекомендую або розділити об'єкт на два окремих дзвінки useStateабо замість цього використовувати useReducer.
Джаред Сміт

Так - друге це. Просто скористайтеся двома дзвінками, щоб використовуватиState ()
Есбен Сков Педерсен

const [state, ...], а потім посилаючись на нього в сетері ... Він буде використовувати той самий стан весь час.
Йоганнес Кун

Кращий спосіб дії: використовуйте 2 окремих useStateдзвінки, по одному для кожної "змінної".
Діма Тіснек

Відповіді:


30

Ласкаво просимо в пекло про закриття . Ця проблема трапляється тому, що щоразу, коли 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((prevState) => ({ ...prevState, value1: "new 1" }));
  };
  const changeValue2 = () => {
    setState((prevState) => ({ ...prevState, value2: "new 2" }));
  };

  // ...
};

Ви можете знайти більш широке обговорення цього питання щодо закриття тут і тут .


Зворотний виклик із гачком useState здається недокументованою функцією. Ви впевнені, що це працює?
HMR

@HMR Так, це працює і це задокументовано на іншій сторінці. Подивіться розділ "Функціональні оновлення" тут: reactjs.org/docs/hooks-reference.html ("Якщо новий стан обчислюється за допомогою попереднього стану, ви можете передати функцію setState")
Альберто Триндада Таварес

1
@AlbertoTrindadeTavares Так, я дивився на документи також, не міг нічого знайти. Дякую за відповідь!
Bartek

1
Ваше перше рішення не просто "просте рішення", це правильний метод. Другий працював би лише в тому випадку, якщо компонент розроблений як синглтон, і навіть тоді я не впевнений у цьому, оскільки стан щоразу стає новим об'єктом.
Scimonster

1
Дякую @AlbertoTrindadeTavares! Хороший
Хосе Сальгадо

19

Ваші функції повинні бути такими:

const changeValue1 = () => {
    setState((prevState) => ({ ...prevState, value1: "new 1" }));
};
const changeValue2 = () => {
    setState((prevState) => ({ ...prevState, value2: "new 2" }));
};

Таким чином, ви переконайтесь, що ви не пропускаєте жодного наявного властивості в поточному стані, використовуючи попередній стан, коли дія виконується. Крім того, ви уникаєте керувати закриттями.


6

Коли 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>


3

Що відбувається, це те, що обидва changeValue1і changeValue2бачать стан від візуалізації, в якому вони були створені , тож коли ваш компонент візуалізує вперше ці 2 функції бачать:

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"}.

джерело

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.