简体   繁体   English

反应钩子状态不使​​用最新的

[英]React hooks state not using latest

I have the below code where I want to create a list of tags.我有下面的代码,我想在其中创建标签列表。 In this example I'm fetching a list of tags in setAllTags() and then a number of available tags in setAvailableTags() .在这个例子中,我获取的标签列表setAllTags()然后在多个可用标签setAvailableTags()

Then problem that I have is that when setAvailableTags() is run it will remove the tags that was fetched in setAllTags() .然后我setAvailableTags()问题是,当setAvailableTags()运行时,它将删除在setAllTags()获取的标签。 It seems like the state that I set in setAllTags() is not used when setAvailableTags() is settings it's state.好像是我在设定的状态setAllTags()当不使用setAvailableTags()是设置它的状态。

Any idea what I can do to fix this?知道我能做些什么来解决这个问题吗?

https://codesandbox.io/s/rj40lz6554 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"));

The console is logged with控制台记录为

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

setTags changes the internal react state and doesn't change the value of tags directly. setTags改变内部反应状态并且不直接改变tags的值。 So it doesn't get updated till the next render.所以直到下一次渲染它才会更新。

Use this call instead:请改用此调用:

setTags(currentTags => ({...currentTags, all}));

And do the same with available .并对available做同样的事情。

In the react docs, you can see how useEffect works ( https://reactjs.org/docs/hooks-effect.html )在 react 文档中,您可以看到useEffect是如何工作的( https://reactjs.org/docs/hooks-effect.html

Experienced JavaScript developers might notice that the function passed to useEffect is going to be different on every render.有经验的 JavaScript 开发人员可能会注意到,传递给 useEffect 的函数在每次渲染时都会有所不同。 This is intentional.这是故意的。 In fact, this is what lets us read the count value from inside the effect without worrying about it getting stale.事实上,这就是让我们从效果内部读取计数值而不必担心它变得陈旧的原因。 Every time we re-render, we schedule a different effect, replacing the previous one.每次重新渲染时,我们都会安排不同的效果,替换之前的效果。 In a way, this makes the effects behave more like a part of the render result — each effect “belongs” to a particular render.在某种程度上,这使得效果更像是渲染结果的一部分——每个效果“属于”特定的渲染。 We will see more clearly why this is useful later on this page.我们将在本页后面更清楚地看到为什么这很有用。

What this means, is that each side effect within your render function will only have access to the initial result of useState .这意味着渲染函数中的每个副作用只能访问useState的初始结果。 Remember that react states are immutable, so it doesn't make sense for you to update the state, and then try and use the updated version within the same render cycle.请记住,react 状态是不可变的,因此更新状态然后尝试在同一渲染周期内使用更新的版本对您来说没有意义。

You can see this by simply calling:您可以通过简单地调用来查看:

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

You should notice that tags does not change at all.您应该注意到tags根本没有改变。

Personally I would use hook conventions here, and separate your useState out into two separate variables:我个人会在这里使用钩子约定,并将您的 useState 分成两个单独的变量:

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

This makes your effects more explicit (as they only need to update the one variable), and improves readability.这使您的效果更加明确(因为它们只需要更新一个变量),并提高可读性。

from: https://reactjs.org/docs/hooks-reference.html#usestate来自: https : //reactjs.org/docs/hooks-reference.html#usestate

Unlike the setState method found in class components, useState does not automatically merge update objects.与类组件中的 setState 方法不同,useState 不会自动合并更新对象。 You can replicate this behavior by combining the function updater form with object spread syntax:您可以通过将函数更新程序形式与对象传播语法相结合来复制此行为:

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

Another option is useReducer, which is more suited for managing state objects that contain multiple sub-values.另一种选择是 useReducer,它更适合管理包含多个子值的状态对象。

And if you want to setAvailableTags() to be based on setAllTags() you should do useEffect(setAvailableTags, [tags.all])如果你想setAvailableTags()基于setAllTags()你应该做useEffect(setAvailableTags, [tags.all])

working example: https://codesandbox.io/s/5vzr20x99p?from-embed工作示例: https : //codesandbox.io/s/5vzr20x99p?from-embed

The problem is that when you useEffect with second parameter [], the function will be called only the first time the component is rendered.问题是当你使用第二个参数 [] 时,函数只会在第一次渲染组件时被调用。 But because you use setState in it the component is rerendered, but the useEffect doesn't get called and so it won't update properly.但是因为您在其中使用了 setState 组件被重新渲染,但是 useEffect 不会被调用,因此它不会正确更新。 One way you can fix your problem is by not using setState in each of the function but instead return the data to a another function which then sets the state like so.解决问题的一种方法是不在每个函数中使用 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);

And you can use Promise.all() if you need to fetch the data from an API.如果您需要从 API 获取数据,您可以使用 Promise.all()。

For the initial render, your tags is对于初始渲染,您的tags

{
  all: [],
  availableTags:[]
}

So for both the useEffect functions update this initial object.所以对于这两个useEffect函数更新这个初始对象。 And your useEffect functions are not called on state update.并且您的useEffect函数不会在状态更新时调用。

You can use them like following:您可以像下面这样使用它们:

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

useEffect(setAllTags, []);
useEffect(setAvailableTags, [tags.all]);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM