Uncaught Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement in React Hooks

Uncaught Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement in React Hooks

鉴于以下组件,当我按下年龄选择器并将值更改为 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 (react-dom.development.js:18987)
    at batchedUpdates (react-dom.development.js:2210)
    at dispatchEvent (react-dom.development.js:4946)
    at interactiveUpdates (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>

问题是在第一次渲染中,调用了 3 个 useState 挂钩 - nameagelicense 但在年龄更改为一个值之后低于 16,不再调用 licenseuseState,导致仅调用前 2 个挂钩。作为 React docs state:

Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls.

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

解决方案是将 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>

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

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

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

然后改成

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

那么错误就不会发生了

我已经通过更改 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>
}

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

所以我也遇到了这个错误,不过跟hook被condition包裹无关。相反,这是由于我的键值不正确造成的。

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

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

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

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

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

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


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

      useEffect( () => {
        setPagedata( data );
      }, [data]);

而且有效!