重新渲染前如何使用useReducer的最新状态

时间:2019-09-01 16:29:45

标签: react-hooks

我有两个reducer动作,我想一个接一个地分派。第一个修改状态,然后第二个使用修改后的状态的一部分进行另一次修改。困难在于调用第二个调度时,它仍然具有旧的过时状态,因此无法正确更新该状态。

下面是一个示例(也可以在此处找到https://codesandbox.io/s/react-usereducer-hqtc2),其中有一个对话列表以及一个被视为“活动”对话的注释:

import React, { useReducer } from "react";

const reducer = (state, action) => {
  switch (action.type) {
    case "removeConversation":
      return {
        ...state,
        conversations: state.conversations.filter(
          c => c.title !== action.payload
        )
      };
    case "setActive":
      return {
        ...state,
        activeConversation: action.payload
      };
    default:
      return state;
  }
};

export default function Conversations() {
  const [{ conversations, activeConversation }, dispatch] = useReducer(
    reducer,
    {
      conversations: [
        { title: "James" },
        { title: "John" },
        { title: "Mindy" }
      ],
      activeConversation: { title: "James" }
    }
  );

  function removeConversation() {
    dispatch({ type: "removeConversation", payload: activeConversation.title });
    dispatch({ type: "setActive", payload: conversations[0] });
  }

  return (
    <div>
      Active conversation: {activeConversation.title}
      <button onClick={removeConversation}>Remove</button>
      <ul>
        {conversations.map(conversation => (
          <li key={conversation.title}>{conversation.title}</li>
        ))}
      </ul>
    </div>
  );
}

在这里,当我单击“删除对话”按钮时,我要删除活动对话,然后将活动对话设置为列表顶部的对话。但是,在这里,当第一个调度从列表中删除会话时,第二个调度将active设置为conversations[0],该会话仍包含删除的值(因为状态尚未更新)。结果,即使活动会话已从列表中删除,它仍将活动会话保持为以前的状态。

我可能可以将逻辑合并为一个动作,然后在其中执行所有操作(删除对话,并将活动设置为一个活动),但是理想情况下,我希望让我的reducer动作尽可能地承担一个责任。

有什么方法可以使第二个调度调用具有最新版本的状态,从而不会发生这种问题?

1 个答案:

答案 0 :(得分:0)

如果您将useEffect()当作setState的第二个参数(来自基于类的组件),可能会有所帮助。

如果要使用最新状态进行操作,请使用useEffect(),该状态会在状态更改时出现:

const {
  useState,
  useEffect
} = React;

function App() {
  const [count, setCount] = useState(0);
  const decrement = () => setCount(count-1);
  const increment = () => setCount(count+1);
  
  useEffect(() => {
    console.log("useEffect", count);
  }, [count]);
  console.log("render", count);
  
  return ( 
    <div className="App">
      <p>{count}</p> 
      <button onClick={decrement}>-</button> 
      <button onClick={increment}>+</button> 
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render( < App / > , rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>

<div id="root"></div>

Some further info on useEffect()