【问题标题】:Uncaught Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement in React Hooks未捕获的错误:渲染的钩子少于预期。这可能是由 React Hooks 中意外的提前返回语句引起的
【发布时间】:2019-04-27 14:34:22
【问题描述】:

给定以下组件,当我按下年龄选择器并将值更改为 15,这样我呈现一个没有驾驶执照字段的表单时,我收到错误:

Uncaught Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement.
    at invariant (react-dom.development.js:55)
    at finishHooks (react-dom.development.js:11581)
    at updateFunctionComponent (react-dom.development.js:14262)
    at beginWork (react-dom.development.js:15103)
    at performUnitOfWork (react-dom.development.js:17817)
    at workLoop (react-dom.development.js:17857)
    at HTMLUnknownElement.callCallback (react-dom.development.js:149)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:199)
    at invokeGuardedCallback (react-dom.development.js:256)
    at replayUnitOfWork (react-dom.development.js:17113)
    at renderRoot (react-dom.development.js:17957)
    at performWorkOnRoot (react-dom.development.js:18808)
    at performWork (react-dom.development.js:18716)
    at flushInteractiveUpdates$1 (react-dom.development.js:18987)
    at batchedUpdates (react-dom.development.js:2210)
    at dispatchEvent (react-dom.development.js:4946)
    at interactiveUpdates$1 (react-dom.development.js:18974)
    at interactiveUpdates (react-dom.development.js:2217)
    at dispatchInteractiveEvent (react-dom.development.js:4923)

示例代码如下:

const {useState} = React;

function App() {
  const [name, setName] = useState('Mary');
  const [age, setAge] = useState(16);

  if (age < 16) {
    return (
      <div>
        Name:{' '}
        <input
          value={name}
          onChange={e => {
            setName(e.target.value);
          }}
        />
        <br />
        Age:{' '}
        <input
          value={age}
          type="number"
          onChange={e => {
            setAge(+e.target.value);
          }}
        />
      </div>
    );
  }

  const [license, setLicense] = useState('A123456');

  return (
    <div>
      Name:{' '}
      <input
        value={name}
        onChange={e => {
          setName(e.target.value);
        }}
      />
      <br />
      Age:{' '}
      <input
        value={age}
        type="number"
        onChange={e => {
          setAge(+e.target.value);
        }}
      />
      <br />
      Driver License:{' '}
      <input
        value={license}
        onChange={e => {
          setLicense(e.target.value);
        }}
      />
    </div>
  );
}

ReactDOM.render(<App />, document.querySelector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>

【问题讨论】:

    标签: javascript reactjs react-hooks


    【解决方案1】:

    问题在于,在第一次渲染中,调用了 3 个 useState 挂钩 - nameagelicense 但在年龄更改为低于 16 的值后,useState 的 @987654328 @ 不再被调用,导致只调用前 2 个钩子。作为React docs state

    不要在循环、条件或嵌套函数中调用 Hooks。相反,请始终在 React 函数的顶层使用 Hooks。通过遵循此规则,您可以确保每次渲染组件时都以相同的顺序调用 Hook。这就是让 React 在多个 useStateuseEffect 调用之间正确保留 Hooks 状态的原因。

    调用钩子的顺序很重要,如果我们编写的代码导致钩子不被调用,React 将无法将钩子调用与其值匹配。

    解决方案是将license 挂钩移动到函数的顶部,以便无论是否需要它都会被调用。

    const {useState} = React;
    
    function App() {
      const [name, setName] = useState('Mary');
      const [age, setAge] = useState(16);
      const [license, setLicense] = useState('A123456');
    
      return (
        <div>
          Name:{' '}
          <input
            value={name}
            onChange={e => {
              setName(e.target.value);
            }}
          />
          <br />
          Age:{' '}
          <input
            value={age}
            type="number"
            onChange={e => {
              setAge(+e.target.value);
            }}
          />
          {age >= 16 && <span>
            <br />
            Driver License:{' '}
            <input
              value={license}
              onChange={e => {
                setLicense(e.target.value);
              }}
            /></span>
           }
        </div>
      );
    }
    
    ReactDOM.render(<App />, document.querySelector('#app'));
    <script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
    <div id="app"></div>

    【讨论】:

    • 我遇到了问题,解决了它并与社区分享了解决方案和分析。这显然受到 StackOverflow 创始人的鼓励 - stackoverflow.blog/2011/07/01/…
    • +one @YangshunTay 我很高兴你这样做了,因为我自己也遇到了这个
    • 感谢这个清晰的例子!我已经多次遇到这个问题,但你的问答让我有一个 AHA 时刻,即使是个人辅导也从未有过。
    【解决方案2】:

    我已经通过更改 React Hooks 的顺序解决了这个问题。

    所以我改变了代码

    function(){
    
    const [loading ,setLoading] = React.useState(false);
    
    if(loading){
    return "loading ....."
    }
    
    useEffect(()=>{
      if(condition) {
        doSomething();
      }
    }, []);
    
    return <div>component</div>
    }
    

    function(){
    
    const [loading ,setLoading] = React.useState(false);
    
    
    useEffect(()=>{
      if(condition) {
        doSomething();
      }
    }, []);
    
    
    if(loading){
    return "loading ....."
    }
    return <div>component</div>
    }
    

    所以,当加载变为真时,useEffect 将不会运行,并且会抛出错误。但是如果我在 useEffect 之后声明加载 JSX 元素,那么它将完美运行。

    【讨论】:

    • 这正是发生在我身上的事情,感谢您的回答。
    • 这有助于我了解在 useEffect 之前我有退货。谢谢!
    • 这正是我的情况,我在 useEffect 之前有一个 if 条件返回语句 ?
    【解决方案3】:

    确保您没有有条件地运行useEffect

    例如,如果您有如下代码:

    if(condition) {
      useEffect(()=>{
        doSomething();
      }, []);
    }
    

    然后改成

    useEffect(()=>{
      if(condition) {
        doSomething();
      }
    }, []);
    

    那么错误就不会发生了。

    【讨论】:

    • 这个建议帮助了我。谢谢!
    【解决方案4】:

    所以我也遇到了这个错误,但它与钩子被包裹在一个条件中无关。相反,这是由于我的键值错误造成的。

    我有一个组件,它通过拖放将项目从一个列表移动到另一个列表。但是,其中一些项目被锁定了,所以我只是删除了这些元素中对我的钩子的引用。

    问题是我使用索引作为键,因此当我在顶部拖动一个新元素时,它得到与锁定元素相同的键,ref 的值被更改,React 抱怨上述错误。

    所以我对遇到这个问题的人的回答是 - 检查您是否拥有真正唯一的密钥!

    【讨论】:

      【解决方案5】:

      今天我刚刚遇到这个错误,我通过改变钩子的位置来修复它:useEffect() 从函数的中间到头部。并确保不要在循环中使用钩子。

      如果某些值在重新渲染之间没有改变,你可以告诉 React 跳过应用效果。为此,请将数组作为可选的第二个参数传递给 useEffect:

            useEffect( () => {
              dispatch({type: 'GET_PAGE', slug: Param});
              setPagedata( data );
            });
      

      
            useEffect( () => {
              dispatch({type: 'GET_PAGE', slug: Param});
            }, [Param]);
      
            useEffect( () => {
              setPagedata( data );
            }, [data]);
      

      它有效!

      【讨论】:

        猜你喜欢
        • 2022-11-20
        • 2020-05-10
        • 2021-11-15
        • 2021-10-03
        • 2021-12-14
        • 2022-06-13
        • 2021-05-09
        • 2020-05-26
        • 2023-01-05
        相关资源
        最近更新 更多