首页 > 解决方案 > 在 Redux 道具更改上使用 useEffect 设置本地状态

问题描述

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

我有以下代码:

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 中想要设置 de Redux 状态diagram(这有效),然后我有另一个 useEffect 钩子,它将从名为 next 的图表属性中获取一个列表,instances我循环遍历这些instances并执行获取以获取该实例的状态并将此状态添加到statusList. 最后我instances使用setinstances(statusList)

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

安慰

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

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

标签: reactjsreduxuse-effectuse-state

解决方案


如果你有多个异步操作,你可以使用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)

推荐阅读