立即更新 useState

时间:2021-03-12 20:49:47

标签: reactjs react-hooks react-select

useState 不会立即更新状态。

我正在使用 react-select,我需要根据请求的结果选择带有 (multi) 选项的组件。

出于这个原因,我创建了状态 defaultOptions,以存储 queues 常量的值。

原来在加载组件的时候,数值只显示了第二次。

我在 console.log 中创建了一个 queues,返回的结果与空不同。

我对 defaultOptions 状态做了同样的处理,返回为空。

我创建了一个 codesandbox 以便更好地查看。

const options = [
  {
    label: "Queue 1",
    value: 1
  },
  {
    label: "Queue 2",
    value: 2
  },
  {
    label: "Queue 3",
    value: 3
  },
  {
    label: "Queue 4",
    value: 4
  },
  {
    label: "Queue 5",
    value: 5
  }
];

const CustomSelect = (props) => <Select className="custom-select" {...props} />;

const baseUrl =
  "https://my-json-server.typicode.com/wagnerfillio/api-json/posts";

const App = () => {
  const userId = 1;
  const initialValues = {
    name: ""
  };
  const [user, setUser] = useState(initialValues);
  const [defaultOptions, setDefaultOptions] = useState([]);
  const [selectedQueue, setSelectedQueue] = useState([]);

  useEffect(() => {
    (async () => {
      if (!userId) return;
      try {
        const { data } = await axios.get(`${baseUrl}/${userId}`);
        setUser((prevState) => {
          return { ...prevState, ...data };
        });

        const queues = data.queues.map((q) => ({
          value: q.id,
          label: q.name
        }));

        // Here there is a different result than emptiness
        console.log(queues);
        setDefaultOptions(queues);
      } catch (err) {
        console.log(err);
      }
    })();

    return () => {
      setUser(initialValues);
    };
  }, []);

  // Here is an empty result
  console.log(defaultOptions);

  const handleChange = async (e) => {
    const value = e.map((x) => x.value);
    console.log(value);
    setSelectedQueue(value);
  };

  return (
    <div className="App">
      Multiselect:
      <CustomSelect
        options={options}
        defaultValue={defaultOptions}
        onChange={handleChange}
        isMulti
      />
    </div>
  );
};
export default App;

5 个答案:

答案 0 :(得分:3)

React 在调用 setState 时不会立即更新状态,有时可能需要一段时间。如果你想在设置新状态后做一些事情,你可以使用 useEffect 来确定状态是否像这样改变:

    const [ queues, setQueues ] = useState([])

    useEffect(()=>{
        /* it will be called when queues did update */
    },[queues] )

    const someHandler = ( newValue ) => setState(newValue)

答案 1 :(得分:2)

添加到其他答案:

在类组件中,您可以在添加新状态后添加回调,例如:

  this.setState(newStateObject, yourcallback)

但在函数组件中,您可以在某些值更改后调用“回调”(不是真正的回调,而是某种回调),例如

// it means this callback will be called when there is change on queue.
React.useEffect(yourCallback,[queue])
.
.
.

// you set it somewhere
 setUserQueues(newQueues);

一切顺利。

别无选择(除非您想Promise)但React.useEffect

答案 2 :(得分:1)

setState 的闭包和异步性质

您遇到的是 closures(在渲染期间如何在函数中捕获值)和 setState 的异步性质的组合。

请参阅此 Codesandbox 以获取工作示例

考虑这个测试组件

const TestComponent = (props) => {
  const [count, setCount] = useState(0);

  const countUp = () => {
    console.log(`count before: ${count}`);
    setCount((prevState) => prevState + 1);
    console.log(`count after: ${count}`);
  };

  return (
    <>
      <button onClick={countUp}>Click Me</button>
      <div>{count}</div>
    </>
  );
};

测试组件是您用来说明闭包和 setState 异步性质的简化版本,但这些想法可以外推到您的用例中。

当一个组件被渲染时,每个函数都被创建为一个闭包。考虑第一次渲染时的函数 countUp。由于 useState(0) 中的 count 被初始化为 0,因此将所有 count 实例替换为 0,以查看它在初始渲染的闭包中的外观。

 const countUp = () => {
    console.log(`count before: ${0}`);
    setCount((0) => 0 + 1);
    console.log(`count after: ${0}`);
  };

设置count前后的Logging count,可以看到设置count前和设置count后两个log都会显示0。

setCount 是异步的,这基本上意味着:调用 setCount 会让 React 知道它需要安排渲染,然后它会修改 count 的状态并在下一次渲染时使用 count 的值更新闭包。

因此,初始渲染将如下所示

 const countUp = () => {
    console.log(`count before: 0`);
    setCount((0) => 0 + 1);
    console.log(`count after: 0`);
  };

当 countUp 被调用时,该函数将记录 count 的值,当该函数闭包被创建时,并会让 react 知道它需要重新渲染,所以控制台看起来像这样

count before: 0 
count after: 0 

React 将重新渲染并因此更新 count 的值并重新创建 countUp 的闭包,如下所示(替换 count 的值)。然后这将使用最新的 count 值更新任何可视组件显示为 1

 const countUp = () => {
    console.log(`count before: 1`);
    setCount((1) => 1 + 1);
    console.log(`count after: 1`);
  };

并且将在每次点击计数按钮时继续这样做。

这是来自 codeSandbox 的片段。请注意控制台如何从初始渲染关闭控制台日志中记录 0,但由于 UI 的异步渲染,count 的显示值在单击一次后显示为 1。< /p>

enter image description here

如果你想看到值的最新渲染版本,最好使用 useEffect 来记录值,一旦 setState 被调用,这将在 React 的渲染阶段发生

useEffect(() => {
  console.log(count); //this will always show the latest state in the console, since it reacts to a change in count after the asynchronous call of setState.
},[count])

答案 3 :(得分:0)

您需要在 useEffect 钩子中使用一个参数,并且只有在进行了一些更改时才重新渲染。下面是一个带有 count 变量的示例,并且仅当计数值发生变化时钩子才会重新渲染。

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes

答案 4 :(得分:0)

问题是 await api.get() 将返回一个承诺,因此当 setUserQueues(queues); 行时,常量数据不会有它的数据集;正在运行。

你应该这样做:

 api.get(`/users/${userId}`).then(data=>{

  setUser((prevState) => {
    return { ...prevState, ...data };
  });

  const queues = data.queues.map((q) => ({
    value: q.id,
    label: q.name,
  }));
  setUserQueues(queues);

  console.log(queues);
  console.log(userQueues);});