javascript - Okänt fel: Renderade färre hooks än väntat. Detta kan orsakas av ett oavsiktligt tidigt return-statement i React Hooks


Givet följande komponent, när jag trycker ner på åldersväljaren och ändrar värdet till 15, så att jag renderar ett formulär utan fältet för körkort, får jag felet:

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)

Exempel på kod nedan:

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/[email /umd/react.development.js"></script>
<script src="https://unpkg.com/[email /umd/react-dom.development.js"></script>
<div id="app"></div>

Svar #1

Problemet är att vid den första renderingen anropades 3 useState-hookar - name, age och license men efter att åldern ändras till ett värde under 16, anropas inte useState för license längre, vilket resulterar i att endast de första 2 hookarna anropas. Som React-dokumentationen säger:

Anropa inte Hooks inuti loopar, villkor eller nästlade funktioner. Använd istället alltid Hooks på den översta nivån av din React-funktion. Genom att följa denna regel säkerställer du att Hooks anropas i samma ordning varje gång en komponent renderas. Det är detta som gör att React korrekt kan bevara tillståndet för Hooks mellan flera useState och useEffect-anrop.

Ordningen på de hooks som kallas är viktig, och om vi skriver kod som gör att hooks inte kallas, kommer React inte att kunna matcha hook-anropet med dess värden.

Lösningen är att flytta license-hooken upp till toppen av funktionen så att den kallas oavsett om den behövs eller inte.

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/[email /umd/react.development.js"></script>
<script src="https://unpkg.com/[email /umd/react-dom.development.js"></script>
<div id="app"></div>

Svar #2

Jag har löst detta problem genom att ändra ordningen på React Hooks. useEffect kom efter ett tidigt return.

Så jag har ändrat koden från

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

till

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

    return <div>component</div>
}

Så, när loading blir sant kommer useEffect inte att köras och det kommer att ge ett fel. Men om jag deklarerar loading JSX-elementet efter useEffect så kommer det att fungera.

Svar #3

Se till att du inte körde useEffect villkorligt.

Till exempel, om du har någon kod som följande:

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

Ändra det sedan till

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

Då skulle felet inte inträffa.

Relaterade artiklar