立即更新 useState

5

useState不会立即更新状态。

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

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

事实证明,在加载组件时,只有第二次才显示值。

我在queues中进行了console.log,返回的结果与空不同。

我用相同的方式处理了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;

1
useState在第一次渲染后触发。如果您想立即开始使用数据,或者您预取状态中的选项,然后再进行渲染,或者在同一组件内添加一个isLoading作为初始渲染,并在第一次获取后显示。在这里,您只有在打开之后才会运行该useEffect。 - quirimmo
但是在这里,我获取数据 const {data} = await api.get('/users/${userId}');,并在获取数据后将结果添加到状态 setUserQueues` 中。虽然我真的尝试了其他方法,但我无法以其他方式完成它。 - Wagner Fillio
我认为建议的是你可以像这样做:if (!userQueues) return <div>Loading</div> 或者什么都不做,这样就不会出现没有信息的渲染。你不需要将数据移动到第一个渲染位置,而是将第一个渲染位置移到数据处。 - Marian
你的返回语句中 queues 是在哪里定义的? - Mike
queues is declared here const queues = data.queues.map((q) => ({... - Wagner Fillio
兄弟,看一下这个:https://dev59.com/C1QJ5IYBdhLWcg3wVEZ0 - Marcus.Aurelianus
6个回答

7

当你调用setState时,React不会立即更新状态,有时可能需要一些时间。如果你想在设置新状态后执行某些操作,可以使用useEffect来确定状态是否已更改,如下所示:

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

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

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

你的代码注释说:“……当队列将被更新时”,这是不正确的:它将在队列已经更新时被调用。这是微妙但有影响力的区别。 - lvilasboas
@lvilasboas 谢谢你指出来。我不是本地人 :) - Kishieel
FYI... "subtle" - haventchecked

3

闭包和setState的异步性质

你所经历的是闭包(在渲染期间如何在函数内部捕获值)和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。由于count在useState(0)中初始化为0,因此将所有count实例替换为0,以查看它在初始渲染的闭包中的样子。
 const countUp = () => {
    console.log(`count before: ${0}`);
    setCount((0) => 0 + 1);
    console.log(`count after: ${0}`);
  };

在设置计数之前和之后记录计数,您可以看到两个日志都会在设置计数之前和之后指示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的值,并重新创建闭包以显示如下(用count的值替换)。然后,任何可视化组件都会更新为最新的计数值1
 const countUp = () => {
    console.log(`count before: 1`);
    setCount((1) => 1 + 1);
    console.log(`count after: 1`);
  };

“每次单击 countUp 按钮时,它都会继续执行此操作。 这是来自 codeSandbox 的代码片段。请注意,控制台已从初始渲染闭包控制台日志记录了 0,但由于 UI 的异步渲染,单击一次后显示的 count 值显示为 1。”

enter image description here

如果你想要看到最新渲染版本的值,最好使用 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])

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


0

你需要在 useEffect 钩子内使用一个参数,并且只有在某些更改发生时重新渲染。以下是一个示例,其中包含 count 变量,钩子仅在计数值发生更改时重新渲染。

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

0
问题在于,await api.get()将返回一个Promise,因此当运行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);});

0
如果你想在同一个函数中更新状态并使用更新后的值,那么你可以在设置状态时将值存储在一个变量中。
const [count, setCount] = useState(0)

const incrementCount = () => {
 let updatedCount;
 setCount((prevCount) => {
   const newCount = prevCount + 1;
   updatedCount = newCount;
   return newCount; // Return the new value
 })
 console.log(updatedCount)
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接