【问题标题】:Update useState immediately立即更新 useState
【发布时间】:2021-03-25 09:09:21
【问题描述】:

useState 不会立即更新状态。

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

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

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

我在queues中做了一个console.log,返回与空不一样。

我对@9​​87654329@ 状态做了同样的事情,返回是空的。

我创建了一个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;

【问题讨论】:

  • useState 在第一次渲染后触发。如果您想立即使用数据开始,或者您在状态中预取选项然后进行渲染,或者在您添加的同一个组件中加载作为初始渲染并在您第一次获取后显示。在这里,您仅在打开后才运行 useEffect
  • 但在这里我得到了数据const {data} = await api.get ('/users/${userId}');` 得到数据后,我将结果添加到状态setUserQueues。尽管我真的尝试过,但我无法做到这一点。
  • 我认为建议您执行if (!userQueues) return &lt;div&gt;Loading&lt;/div&gt; 之类的操作或什么都不做,这样就不会出现没有信息的渲染。您不会将数据移动到第一个渲染。您将第一个渲染移至数据。
  • 在哪里为您的退货声明定义了queues
  • queues 在这里声明 const queues = data.queues.map((q) =&gt; ({...

标签: reactjs react-hooks react-select


【解决方案1】:

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

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

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

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

【讨论】:

  • 您的代码注释说“......当队列将被更新”,这是不正确的:它会在队列确实更新时被调用。这是一个实用但有影响力的区别。
  • @lvilasboas 感谢您指出我。我不是本地人:)
  • 仅供参考...“微妙”
【解决方案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

【讨论】:

    【解决方案3】:

    setState 的闭包和异步性质

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

    有关工作示例,请参阅此 Codesandbox

    考虑一下这个TestComponent

    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前后的loging count,可以看到两个log在设置count之前和“设置”count之后都会显示为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

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

    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])
    

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

        【解决方案5】:

        问题是 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);});
        

        【讨论】:

          猜你喜欢
          • 2020-10-16
          • 1970-01-01
          • 2021-05-20
          • 1970-01-01
          • 1970-01-01
          • 2022-08-14
          • 1970-01-01
          • 2020-10-09
          • 1970-01-01
          相关资源
          最近更新 更多