Реагировать состояние хуков не используя последние - PullRequest
3 голосов
/ 18 марта 2019

У меня есть код ниже, где я хочу создать список тегов. В этом примере я выбираю список тегов в setAllTags(), а затем количество доступных тегов в setAvailableTags().

Тогда у меня проблема в том, что при запуске setAvailableTags() он удаляет теги, которые были извлечены в setAllTags(). Кажется, что состояние, которое я установил в setAllTags(), не используется, когда setAvailableTags() - это настройки.

Есть идеи, что я могу сделать, чтобы это исправить?

https://codesandbox.io/s/rj40lz6554

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

const Search = () => {
  const [tags, setTags] = useState({
    all: [],
    available: []
  });

  const setAllTags = () => {
    const all = ["tag 1", "tag 2", "tag 3"];
    const newValue = {
      ...tags,
      all
    };
    console.log("setting all tags to ", newValue);
    setTags(newValue);
  };

  const setAvailableTags = () => {
    const available = ["tag 1", "tag 2"];
    const newValue = {
      ...tags,
      available
    };
    console.log("setting available tags to", newValue);
    setTags(newValue);
  };

  useEffect(setAllTags, []);
  useEffect(setAvailableTags, []);

  return (
    <div>
      <div>
        <select placeholder="Tags">
          {tags.all.map((tag, i) => (
            <option key={tag + i} value={tag}>
              {tag}
            </option>
          ))}
        </select>
      </div>
    </div>
  );
};

const App = () => {
  return (
    <div>
      <h1>Hello React!</h1>
      <Search />
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("app"));

Консоль зарегистрирована с

setting all tags to Object {all: Array[3], available: Array[0]}
setting available tags to Object {all: Array[0], available: Array[2]}

Ответы [ 5 ]

3 голосов
/ 18 марта 2019

В реактивных документах вы можете увидеть, как работает useEffect (https://reactjs.org/docs/hooks-effect.html)

Опытные разработчики JavaScript могут заметить, что функция, передаваемая в useEffect, будет отличаться для каждого рендера.на самом деле, это то, что позволяет нам считать значение счетчика изнутри эффекта, не беспокоясь о его устаревании. Каждый раз, когда мы делаем рендеринг, мы планируем новый эффект, заменяя предыдущий. В некотором смысле, это делаетЭффекты ведут себя больше как часть результата рендеринга - каждый эффект «принадлежит» определенному рендеру. Мы увидим более ясно, почему это полезно позже на этой странице.

Что это означает,что каждый побочный эффект в вашей функции рендеринга будет иметь доступ только к начальному результату useState. Помните, что состояния реакции неизменны, поэтому вам не имеет смысла обновлять состояние, а затем пытаться использовать обновленную версию.в том же цикле рендеринга.

Вы можете увидеть это, просто позвонив:

setTags({ all: ['test'] })
console.log(tags)

Вы должны заметить, что tags совсем не меняется.

Лично я бы использовал здесь соглашения о хуках и разделил бы ваше useState на две отдельные переменные:

const [allTags, setAllTags] = useState([])
const [availableTags, setAvailableTags] = useState([])

Это делаетВаши эффекты более явные (так как им нужно обновить только одну переменную) и улучшают читабельность.

1 голос
/ 18 марта 2019

setTags изменяет внутреннее состояние реакции и не изменяет значение tags напрямую. Так что он не обновляется до следующего рендера.

Используйте этот вызов вместо: setTags(currentTags => ({...currentTags, all}));. И сделать то же самое с available.

0 голосов
/ 18 марта 2019

Для начального рендеринга ваш tags равен

{
  all: [],
  availableTags:[]
}

Таким образом, обе функции useEffect обновляют этот начальный объект. И ваши useEffect функции не вызываются при обновлении состояния.

Вы можете использовать их следующим образом:

const setAvailableTags = () => {
  const available = ["tag 1", "tag 2"];
  setTags(prevTags => ({...prevTags, available}));
};

useEffect(setAllTags, []);
useEffect(setAvailableTags, [tags.all]);
0 голосов

Проблема в том, что когда вы используете Effect со вторым параметром [], функция будет вызываться только при первом визуализации компонента. Но поскольку вы используете в нем setState, компонент перерисовывается, но useEffect не вызывается и поэтому не обновляется должным образом. Один из способов решить эту проблему - не использовать setState в каждой функции, а вместо этого возвращать данные другой функции, которая затем устанавливает состояние следующим образом.

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

const Search = () => {
  const [tags, setTags] = useState({
    all: [],
    available: []
  });

  const setAllTags = () => {
    const all = ["tag 1", "tag 2", "tag 3"];
    return all;
  };

  const setAvailableTags = () => {
    const available = ["tag 1", "tag 2"];
    return available;
  };

  useEffect(() => {
    const all = setAllTags();
    const available = setAvailableTags();
    setTags({
      all: all,
      available: available
    });
  }, []);

  return (
    <div>
      <div>
        <select placeholder="Tags">
          {tags.all.map((tag, i) => (
            <option key={tag + i} value={tag}>
              {tag}
            </option>
          ))}
        </select>
      </div>
    </div>
  );
};

const App = () => {
  return (
    <div>
      <h1>Hello React!</h1>
      <Search />
    </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

И вы можете использовать Promise.all (), если вам нужно извлечь данные из API.

0 голосов
/ 18 марта 2019

от: https://reactjs.org/docs/hooks-reference.html#usestate

В отличие от метода setState, найденного в компонентах класса, useState не объединяет объекты обновления автоматически. Вы можете повторить это поведение, комбинируя форму средства обновления функций с синтаксисом распространения объекта:

setState(prevState => {
  // Object.assign would also work
  return {...prevState, ...updatedValues};
});

Другим вариантом является useReducer, который больше подходит для управления объектами состояния, которые содержат несколько значений.

И если вы хотите, чтобы setAvailableTags() был основан на setAllTags(), вам следует сделать useEffect(setAvailableTags, [tags.all])

рабочий пример: https://codesandbox.io/s/5vzr20x99p?from-embed

...