在 Redux 属性更改时使用 useEffect 设置本地状态

Set the local state using useEffect on Redux prop change

我是 React Redux 的新手,我正在尝试 setState 使用 useEffect 钩子对 Redux 中的道具进行更改。

我有以下代码:

const DeploymentOverview = ({diagram, doSetDiagram}) => {
    const { diagram_id } = useParams()
    const [instances, setinstances] = useState(null)
    const [error, seterror] = useState([false, ''])
    
    useEffect(() => {
        GetDiagram(diagram_id).then(d => doSetDiagram(d)).catch(err => seterror([true, err]))
    }, [doSetDiagram])

    useEffect(() => {
        if (diagram) {
            if (diagram.instances) {
                let statusList = []
                diagram.instances.forEach(instance => {
                    InstanceStatus(instance.key)
                    .then(status => statusList.push(status))
                    .catch(err => seterror([true, err]))
                });
                setinstances(statusList)
            }
        }
    }, [diagram])

    return (
        <Container>
            {error[0] ? <Row><Col><Alert variant='danger'>{error[1]}</Alert></Col></Row> : null}
            {instances ? 
            <>
                <Row>
                    <Col>
                        <h1>Deployment of diagram X</h1>
                        <p>There are currently {instances.length} instances associated to this deployment.</p>
                    </Col>
                </Row>
                <Button onClick={setinstances(null)}><FcSynchronize/> refresh status</Button>
                <Table striped bordered hover>
                        <thead>
                            <tr>
                                <th>Status</th>
                                <th>Instance ID</th>
                                <th>Workflow</th>
                                <th>Workflow version</th>
                                <th>Jobs amount</th>
                                <th>Started</th>
                                <th>Ended</th>
                                <th></th>
                            </tr>
                        </thead>
                        <tbody>
                        {instances.map(instance => 
                            <tr>
                                <td>{ <StatusIcon status={instance.status}/> }</td>
                                <td>{instance.id}</td>
                                {/* <td>{instance.workflow.name}</td>
                                <td>{instance.workflow.version}</td> */}
                                {/* <td>{instance.jobs.length}</td> */}
                                <td>{instance.start}</td>
                                <td>{instance.end}</td>
                                <td><a href='/'>Details</a></td>
                            </tr>
                        )}
                    </tbody>
                </Table>
                </> 
                
        : <Loader />}
            
        </Container>
    )
}

const mapStateToProps = state => ({
    diagram: state.drawer.diagram
})

const mapDispatchToProps = {
    doSetDiagram: setDiagram
}

export default connect(mapStateToProps, mapDispatchToProps)(DeploymentOverview)

我在第一个 useEffect 中想要的是设置 diagram 的 de Redux 状态(这可行),然后我有另一个 useEffect 挂钩,它将从一个名为 [= 的图表属性中获取一个列表14=] 接下来我遍历那些 instances 并执行获取以获取该实例的状态并将此状态添加到 statusList。最后,我使用 setinstances(statusList)

设置 instances 状态

所以现在我希望将状态结果列表设置到 instances 中,情况就是这样(也有效吗?)。但随后该值被改回初始值 null...

在我的控制台中,它首先显示空值(好的,初始值),然后是列表(是的!),然后再次显示空值(嗯?)。我在互联网和 useEffect 文档上看到 useEffect 在每次渲染后运行,但我仍然不明白为什么 instances 被设置然后又恢复到初始状态。

我很好奇我做错了什么以及如何解决这个问题。

如果你有多个异步操作,你可以使用 Promise.all:

useEffect(() => {
  if (diagram) {
    if (diagram.instances) {
      Promise.all(
        diagram.instances.map((instance) =>
          InstanceStatus(instance.key)
        )
      )
        .then((instances) => setInstances(instances))
        .catch((err) => setError([true, err]));
    }
  }
}, [diagram]);

这是一个工作示例:

const InstanceStatus = (num) => Promise.resolve(num + 5);
const useEffect = React.useEffect;
const App = ({ diagram }) => {
  const [instances, setInstances] = React.useState(null);
  const [error, setError] = React.useState([false, '']);
  //the exact same code from my answer:
  useEffect(() => {
    if (diagram) {
      if (diagram.instances) {
        Promise.all(
          diagram.instances.map((instance) =>
            InstanceStatus(instance.key)
          )
        )
          .then((instances) => setInstances(instances))
          .catch((err) => setError([true, err]));
      }
    }
  }, [diagram]);
  return (
    <pre>{JSON.stringify(instances, 2, undefined)}</pre>
  );
};
const diagram = {
  instances: [{ key: 1 }, { key: 2 }, { key: 3 }],
};

ReactDOM.render(
  <App diagram={diagram} />,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

你做错的是:

diagram.instances.forEach(instance => {
  InstanceStatus(instance.key)//this is async
  //this executes later when the promise resolves
  //mutating status after it has been set does not 
  //re render your component
  .then(status => statusList.push(status))
  .catch(err => seterror([true, err]))
});
//this executes immediately so statusList is empty
setinstances(statusList)