This is a problem encountered when using function Component and hooks of React during project development. Usually, in the process of writing code, I choose to write the whole page-level files in the form of class, while in the function or logic is relatively simple, such as purely display components, I would have chosen function Component with hooks

Rendered more hooks than during the previous render

In the description of the project actual situation before we together to see a demo of hell to enrage, recently all over the country in a vaccine inoculation, I also the first doses of vaccine inoculation during the qingming festival holiday, need to fill out a form before inoculation, whether there is a in the pregnancy, men and women all want to fill in, but if in the form of an electronic form, If the gender is female, it will not be displayed if the gender is male. The code is as follows:

import React, { Fragment, useState } from 'react'; const Vero = () => { const [ gender, setGender ] = useState('male'); const [ isUnder18, setIsUnder18 ] = useState(0); const [ isOver60, setIsOver60 ] = useState(0); If (gender === 'male') {return (<Fragment> <div> <div>18 years old :</div> <div> <input type="radio" value={1} checked={isUnder18} onChange={ e => { const { target } = e; const { value } = target; setIsUnder18(+value); }} / > < / div > < div > no: < input type = "radio" value = {0} checked = {! isUnder18} onChange={ e => { const { target } = e; const { value } = target; setIsUnder18(+value); }} / > < / div > < / div > < div > < div > 60 years of age or older: < / div > < div > is: <input type="radio" value={1} checked={isOver60} onChange={ e => { const { target } = e; const { value } = target; setIsOver60(+value); }} / > < / div > < div > no: < input type = "radio" value = {0} checked = {! isOver60} onChange={ e => { const { target } = e; const { value } = target; setIsOver60(+value); }} / > < / div > < / div > < div > < div > gender: < / div > < div > m: <input type="radio" value={'male'} checked={gender === 'male'} onChange={ e => { const { target } = e; const { value } = target; setGender(value); }} /> </div> <div> <input type="radio" value={'female'} checked={gender === 'female'} onChange={ e => { const { target } = e; const { value } = target; setGender(value); } } /> </div> </div> </Fragment> ); } const [ gestation, setGestation ] = useState(1); Return (<Fragment> <div> <div>18 </div> <div> is: <input type="radio" value={1} checked={isUnder18} onChange={ e => { const { target } = e; const { value } = target; setIsUnder18(+value); }} / > < / div > < div > no: < input type = "radio" value = {0} checked = {! isUnder18} onChange={ e => { const { target } = e; const { value } = target; setIsUnder18(+value); }} / > < / div > < / div > < div > < div > 60 years old the following: < / div > < div > is: <input type="radio" value={1} checked={isOver60} onChange={ e => { const { target } = e; const { value } = target; setIsOver60(+value); }} / > < / div > < div > no: < input type = "radio" value = {0} checked = {! isOver60} onChange={ e => { const { target } = e; const { value } = target; setIsOver60(+value); }} / > < / div > < / div > < div > < div > gender: < / div > < div > m: <input type="radio" value={'male'} checked={gender === 'male'} onChange={ e => { const { target } = e; const { value } = target; setGender(value); }} /> </div> <div> <input type="radio" value={'female'} checked={gender === 'female'} onChange={ e => { const { target } = e; const { value } = target; setGender(value); }} / > < / div > < / div > < div > < div > pregnancy: < / div > < div > is: <input type="radio" value={1} checked={gestation} onChange={ e => { const { target } = e; const { value } = target; setGestation(+value); }} / > < / div > < div > no: < input type = "radio" value = {0} checked = {! gestation} onChange={ e => { const { target } = e; const { value } = target; setGestation(+value); } } /> </div> </div> </Fragment> ); }; export default Vero;Copy the code

, of course, I just take, for example, after all, we use the react for development, so a demand that wouldn’t be so, no matter what, after all, any skill skilled, you want to make mistakes but is a difficult thing, this is not a Versailles, ha, I want to write the examples of error was thought for a moment to write…

Well… Well… Now, if we change gender, the page will display an error:

React has detected a change in the order of Hooks called by Vero. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: Rules of Hooks

React detected a change in the order in which Vero called the hooks. If not fixed, this will cause some problems and errors. See the hooks rule for details

Rendered more hooks than during the previous render.

More hooks rendered than last time

This program is using three hooks to render:

  const [ gender, setGender ] = useState('male');
  const [ isUnder18, setIsUnder18 ] = useState(0);
  const [ isOver60, setIsOver60 ] = useState(0);
Copy the code

At this point, due to the if judgment, the JSX rendering result returned by our functional component is:

Under 18 years of age and over 60 years of ageCopy the code

We then changed the gender state: gender from male to female, so the Vero function is reexecuted.

  const [ gender, setGender ] = useState('male');
  const [ isUnder18, setIsUnder18 ] = useState(0);
  const [ isOver60, setIsOver60 ] = useState(0);
  const [ gestation, setGestation ] = useState(1);
Copy the code

If condition is no longer met, not executed, rendering result is:

Under 18 years of age and over 60 years of ageCopy the code

However, the program reported an error, and the final render result could not be displayed properly. At this point, we found:

Last rendered 3 hooks:

  const [ gender, setGender ] = useState('male');
  const [ isUnder18, setIsUnder18 ] = useState(0);
  const [ isOver60, setIsOver60 ] = useState(0);
Copy the code

Next render four hooks:

  const [ gender, setGender ] = useState('male');
  const [ isUnder18, setIsUnder18 ] = useState(0);
  const [ isOver60, setIsOver60 ] = useState(0);
  const [ gestation, setGestation ] = useState(1);
Copy the code

That’s one more than last time

const [ gestation, setGestation ] = useState(1);
Copy the code

What’s going on here? Let’s take a look at the document mentioned in the error message:

Only Call Hooks at the Top Level

Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function, before any early returns. By following this rule, You ensure that Hooks are called in the same order each time a component. That’s what allows React to correctly Preserve the state of Hooks between multiple useState and useEffect calls. (If you’re curious, We’ll explain this in depth below.)

Call hooks only at the top

Do not call hooks in loops, conditions, or nested functions. Instead, always use hooks at the top of your React function, before premature return statements. By following this rule, you can be sure that hooks are called in the same order every time you render. This is how React correctly maintains the state of hooks on multiple useState and useEffect calls (we’ll explain this in a later article if you’re curious).

Looking at the explanation, the answer is:

React relies on the order in which Hooks are called

React relies on the order in which hooks are called

Our example works because the order of the Hook calls is the same on every render

Our example works because each hook is rendered in the same order

To return to our own example, the error was reported because the order was out of order. The hooks that were originally called are remembered as Previous Render.

  const [ gender, setGender ] = useState('male');
  const [ isUnder18, setIsUnder18 ] = useState(0);
  const [ isOver60, setIsOver60 ] = useState(0);
Copy the code

Next render, which is called Next render. In this case, if is not true. In this case, the code executes the following if statement:

  const [ gender, setGender ] = useState('male');
  const [ isUnder18, setIsUnder18 ] = useState(0);
  const [ isOver60, setIsOver60 ] = useState(0);
  const [ gestation, setGestation ] = useState(1);
Copy the code

Next Render 1 more hook than Previous Render:

  const [ gestation, setGestation ] = useState(1);
Copy the code

The error result also shows the difference between the two renders:

React cannot rely on the order in which hooks are called because they are not the same

Rendered fewer hooks than expected. This may be caused by an accidental early return statement

We have one change to the original code, Rendered in one place, and the rest remains the same:

if(gender === 'male')
Copy the code

To:

if(gender === 'female')
Copy the code

Also, change our gender state. React will give us the following error:

Rendered fewer hooks than expected. This may be caused by an accidental early return statement.

Why fewer and not more? Previous render JSX with gestation because the if condition was changed and the if statement could not be executed.

  const [ gender, setGender ] = useState('male');
  const [ isUnder18, setIsUnder18 ] = useState(0);
  const [ isOver60, setIsOver60 ] = useState(0);
  const [ gestation, setGestation ] = useState(1);
Copy the code

When we change the gender state to trigger a redraw, the if condition is met, the if statement is executed, the following statement is not executed, the Next render only executes to the return position in the if statement, so this time only 3 hooks are rendered, Which is the top 3 hooks:

  const [ gender, setGender ] = useState('male');
  const [ isUnder18, setIsUnder18 ] = useState(0);
  const [ isOver60, setIsOver60 ] = useState(0);
Copy the code

We can write Previous Render and Next Render ourselves to have a more intuitive comparison:

Previous render Next render
gender gender
isUnder18 isUnder18
isOver60 isOver60
gestation undefined

This allows you to visually see the hooks being rendered in the previous and subsequent renderings

Of course, if this requirement is actually implemented, the correct way to write it is like this:

import React, { Fragment, useState } from 'react'; const Vero = () => { const [ gender, setGender ] = useState('male'); const [ isUnder18, setIsUnder18 ] = useState(0); const [ isOver60, setIsOver60 ] = useState(0); const [ gestation, setGestation ] = useState(1); Return (<Fragment> <div> <div>18 </div> <div> is: <input type="radio" value={1} checked={isUnder18} onChange={ e => { const { target } = e; const { value } = target; setIsUnder18(+value); }} / > < / div > < div > no: < input type = "radio" value = {0} checked = {! isUnder18} onChange={ e => { const { target } = e; const { value } = target; setIsUnder18(+value); }} / > < / div > < / div > < div > < div > 60 years old the following: < / div > < div > is: <input type="radio" value={1} checked={isOver60} onChange={ e => { const { target } = e; const { value } = target; setIsOver60(+value); }} / > < / div > < div > no: < input type = "radio" value = {0} checked = {! isOver60} onChange={ e => { const { target } = e; const { value } = target; setIsOver60(+value); }} / > < / div > < / div > < div > < div > gender: < / div > < div > m: <input type="radio" value={'male'} checked={gender === 'male'} onChange={ e => { const { target } = e; const { value } = target; setGender(value); }} /> </div> <div> <input type="radio" value={'female'} checked={gender === 'female'} onChange={ e => { const { target } = e; const { value } = target; setGender(value); } } /> </div> </div> { gender === 'female' ? (<div> <div> Pregnancy :</div> <div> is: <input type="radio" value={1} checked={gestation} onChange={ e => { const { target } = e; const { value } = target; setGestation(+value); }} / > < / div > < div > no: < input type = "radio" value = {0} checked = {! gestation} onChange={ e => { const { target } = e; const { value } = target; setGestation(+value); } } /> </div> </div> ) : null } </Fragment> ); }; export default Vero;Copy the code

When gender state is male(i.e. first render) :

When we change gender state to female:

Then you can happily go get vaccinated

Scenarios in the project

In a project I was in charge of, I used GraphQL (Apollo-client) for network request and SSR (NextJS) project, which also needed to obtain the user’s geographic location. This uses Baidu map JavaScript API V3.0 class reference _Geolocation, here by the way to make fun of Baidu map is really difficult to use, before using Baidu cloud storage, incomplete documentation, even the use of Mongolian with guess, the function is not complete, but also to change the source code, drunk, When I am writing this article, I find that I cannot find the previous positioning demo, so I attach the documentation of the Geolocation class

For the operation of the user’s location is asynchronous, and we need to render to the client in a program that is, when the browser, according to the orientation of the browser’s own interface failed go IP location, this logic baidu location API help us to package well, we can be used directly, but because it is asynchronous, we need to do so, the process is as follows:

  1. First render a version of the page to the user
  2. Get user coordinates
  3. Request the back-end interface to retrieve the data after the location is obtained
  4. pageloading
  5. End of request Cancelloading
  6. Rerender the page with new data

This page uses function Component and hooks. The project is fine until the logon register is accessed. The logon register was written by another friend and wrapped into a JS file. Using the traditional method of passing in the id of the login registration div, I need to access the Window while rendering to the browser to retrieve the object it wraps to initialize the login registration function

View of the above process, I need to do this action after step 6, because if you do this operation before obtaining coordinates, if the user agrees to obtain the coordinate, got the coordinates to render, then login registration dialog will disappear after initialization, so only after the second render a page which is step 6 to do:

if(loading) { return <LoadingSpin />; } useEffect(() => {// initialize the login registry}, [])Copy the code

At this time, I made a mistake in the demo above. I used hook after conditional judgment of return, at which time the call order would change and react would report an error. However, if I use useEffect, the page would be rendered again when the user coordinates were obtained, resulting in the failure of login registration initialization. At this time, I came up with a method: since loading components need to be rendered when coordinates are obtained and data is requested, I can initialize them in the life cycle of Loading componentWillUnmount

UseEffect (if return) {useEffect (if return);

import { useEffect } from 'react'; import PropTypes from 'prop-types'; import { Spin } from 'antd'; const LoadingSpin = ({ containerStyle = {}, size, willUnmountCb }) => { useEffect( () => { return () => { if(willUnmountCb) { willUnmountCb(); }}; } []); return ( <div style={{ width: '100%', height: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center', ... containerStyle }} > <Spin size={size || 'default'}/> </div> ); }; LoadingSpin.propTypes = { containerStyle: PropTypes.object, size: PropTypes.string, willUnmountCb: PropTypes.func }; export default LoadingSpin;Copy the code

LoadingSpin here is an example of a very typical function component, which is purely presentable. It uses FC, which includes hooks like useState, even with some state. UseEffect:

useEffect( () => { return () => { if(willUnmountCb) { willUnmountCb(); }}; } []);Copy the code

ComponentWillUnmount (componentWillUnmount (), componentWillUnmount (), componentWillUnmount ()), componentWillUnmount (), componentWillUnmount ()), componentWillUnmount (), componentWillUnmount ());

if(loading) { return ( <LoadingSpin size="large" containerStyle={{ width: '100vw', height: '100 vh'}} willUnmountCb = {() = > {/ / login initialization register}} / >); }Copy the code

This approach does not break the sequence of react hooks and does what we need

If you find this article useful to you, please give me a thumbs up, click a favorite, and hope there is no code too hard to write, no requirements too hard to implement