【问题标题】:React updating a components state from another component causes infinite loopReact 从另一个组件更新组件状态会导致无限循环
【发布时间】:2022-01-05 17:48:13
【问题描述】:

我有一个基本上是包含多个步骤的表单向导。该向导分为两部分:标签和内容。当内容组件更改其内部状态时,例如从 incompleteerror 或其他内容,我希望向导更新标签。我遇到的问题是从contents 组件中获取状态,尝试将其保存在wizard 组件中作为状态,以便我可以更新labels 导致无限循环。

虽然我理解这个问题,但我真的不知道如何解决它。这是一个非常最小的示例,我的真实组件使用一些高级功能,例如cloneElement 将道具传递给用户组件,而无需担心设置 10 个不同的道具。到目前为止,这一直完美无缺。

所以我明白每次我更新我的主要组件状态时,它都会重新渲染子组件,这将永远调用相同的设置状态函数。我能做些什么呢?

import React from "react";

import "./styles.css";

// This component containsLabel children
interface LabelState {
  error: boolean;
}
interface LabelGroupProps {
  states: LabelState[],
}
const LabelGroup = (props: LabelGroupProps) => {
  return (<div>I am Comp A</div>)
}

// This component contains Component children
interface ContentState {
  error: boolean;
}
interface ContentGroupProps {
  getStates: (state: ContentState, index: number) => void
}
const ContentGroup = (props: ContentGroupProps) => {
  // Indicate that step 2 has an error
  props.getStates({
    error: true
  }, 2)
  return (<div>I am Comp B</div>)
}

// This is the main wizard that contains both above components
// When the state of the a content component changes, the labels must be updated.
const App = () => {
  const [states, setStates] = React.useState<LabelState[]>([]);

  const getStates = (state: ContentState, index: number) => {
    // This causes an infinite loop
    // The intention is to save this state and then update the labels
    const temp = [];
    temp[index] = state;
    setStates(prev => [...prev, ...temp])
  }

  return (

    <div className="App">
      <LabelGroup states={states}/>
      <ContentGroup getStates={getStates}/>
    </div>
  );
};

export default App;

https://codesandbox.io/s/react-fiddle-forked-1ypi6

【问题讨论】:

    标签: reactjs typescript


    【解决方案1】:

    您的问题是由每次 ContentGroup 呈现时更新状态引起的。任何时候 props 或 state 发生变化,组件都会自动重新渲染。然后组件再次更新状态,然后我们再次渲染,以此类推。我们可以解决这个问题。

    您致电getStates 的目的是什么?你说:

    
    // Indicate that step 2 has an error
    
    

    错误可能是对某些事件的响应。例如,用户在表单上单击了“提交”,而您遇到了自定义验证错误。可能看起来像这样:

    
    const ContentGroup = (props: ContentGroupProps) => {
      // Indicate that step 2 has an error
      return (
        <form
          onSubmit={(event) => {
            event.preventDefault();
            const someErrorCondition = /* snip */;
            props.getStates({
              error: someErrorCondition
            }, 2);
          }}
        >
          {/* snip */}
        </form>
      )
    }
    
    

    这不会导致渲染中的无限循环,因为状态更改不会导致 ContentGroup 再次调用 onSubmit 处理程序。

    有时您希望在组件挂载和卸载时执行初始化和清理:

    
    const ContentGroup = (props: ContentGroupProps) => {
      // Indicate that step 2 has an error
      React.useEffect(() => {
        props.getStates({
          error: true
        }, 2);
        return function cleanup() {
          props.getStates({
            error: false
          }, 2);
        };
      }, []);
      return (
        <form
          onSubmit={(event) => {
            event.preventDefault();
            /* snip */
          }}
        >
          {/* snip */}
        </form>
      )
    }
    
    

    这不会导致无限循环,因为getStates 只会被调用两次:组件挂载时和组件卸载之前。

    请将此视为伪代码,因为有更多关于利用钩子的细节。这是useEffecthttps://reactjs.org/docs/hooks-reference.html#useeffect 的文档 我发布的示例代码不会通过很多 linter,因为它没有将 getStates 声明为钩子依赖数组的一部分。在这种情况下,它实际上很好,因为我们只运行一次效果。对getStates 的进一步更改将被忽略。下面是关于 hook 的依赖数组的更多细节:https://reactjs.org/docs/hooks-reference.html#conditionally-firing-an-effect

    【讨论】:

    • 嗯,我猜你是对的,我自己对随机调用 getStates 的简单测试是愚蠢的,因为它只是在每次渲染时发生,而不是在实际的钩子或调用或任何东西上发生.. duh.. 谢谢你
    猜你喜欢
    • 1970-01-01
    • 2017-10-06
    • 2020-09-06
    • 2019-02-26
    • 2020-07-01
    • 2022-01-19
    • 1970-01-01
    • 1970-01-01
    • 2016-05-12
    相关资源
    最近更新 更多