React hooks report error: Rendered fewer hooks/Rendered more hooks

React hooks report error: Rendered fewer hooks/Rendered more hooks

This is used during project development

react
of
function component
with
hooks
For the problems encountered, in the process of writing code, I will choose to use the entire page-level file
class
In the form of writing, but in the function or logic is relatively simple, such as pure display components, I will choose to use
function component
At the same time supplemented by
hooks

Rendered more hooks than during the previous render

Before describing the actual situation of the project, let s take a look at one by one.HellLocal

demo
, Recently, vaccinations have been launched all over the country. I also received the first dose of the vaccine during the Ching Ming holiday. Before the vaccination, I need to fill out a form. There is one item in it.
Are you in pregnancy
, Both male and female must fill in, but if it is processed in the form of an electronic form, it can be displayed when the gender is female, and not displayed when 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>Under 18:</div> <div> Yes: <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>Over 60 years old:</div> <div> Yes: <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> male: <input type="radio" value={'male'} checked={gender ==='male'} onChange={ e => { const {target} = e; const {value} = target; setGender(value); } } /> </div> <div> Female: <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>Under 18:</div> <div> Yes: <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>Under 60 years old:</div> <div> Yes: <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> male: <input type="radio" value={'male'} checked={gender ==='male'} onChange={ e => { const {target} = e; const {value} = target; setGender(value); } } /> </div> <div> Female: <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> Yes: <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 code

Of course, I'm just giving an example, after all we use

react
For development, such a requirement, you will not write it in any way. After all, if you are proficient in any skill to a certain level, it is more difficult to make mistakes. This is not Versailles. I want to write this example of error reporting. I wrote it after a while...

Huh...cough...close to the subject, now we revise

gender
, Then the page will report 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
Vero
transfer
hook
The order has changed. If it is not fixed, it will cause some problems and errors. For details, please refer to the rules of hooks.

Rendered more hooks than during the previous render.

Rendered more than last time

hooks

So let me take a look at how it is rendered more than the last time. First of all, the program rendering is

3
A
hooks
:

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

At this time due to

if
Judge, ultimately our
Functional component
return
jsx
The rendering result is:

Under 18 Over 60 years old gender Copy code

Then we modified the gender

state
:
gender
, Change its value from the initial value
male
Modified to
female
, Then at this time
Vero
The function is re-executed, and the final rendering result is:
4
A
hooks
:

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

if
The conditions are no longer met, do not execute, and the rendering result is:

Under 18 Over 60 years old gender Pregnancy period Copy code

But the program reported an error, and the final rendering result failed to be displayed normally. At this point, we found:

Last rendered

3
A
hooks
:

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

Next render

4
A
hooks
:

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

Indeed more than last time, more

1
A

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

What is going on here? Let's take a look at the documents 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 renders. 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 only at the top

hooks

Do not call in loops, conditions or nested functions

hooks
. On the contrary, always at you
react
The top of the function, at the premature
return
Use before the statement
hooks
. By following this rule, you can determine every time you render
hooks
Can be called in the same order, which is why
react
Can be in multiple
useState
with
useEffect
Keep correctly in the call
hooks
The reason for the status (if you are curious, we will have an explanation in a later article)

After consulting this explanation, I found that the answer is:

React relies on the order in which Hooks are called

react
rely
hooks
Call sequence

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

Our example can run normally because each rendering

hook
The rendering order is the same

Going back to our own example, the reason why the error was reported was because the order was disrupted, the first call

hooks
The rendering we write as
Previous render
, Rendered at this time
hooks
as follows:

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

Then we modified

gender
This one
state
Then we set off to re-render, this time we write
Next render
, at this time
if
The judgment is not established, the code runs
if
The sentence after that is rendered
hooks
as follows:

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

Next render
ratio
Previous render
More rendering
1
A
hook
:

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

In the result of the error report, the difference between the two renderings is actually displayed:

Our code resulted in before and after

hooks
Inconsistent rendering, at this time
react
Can't rely on
hooks
The order of calling is changed, because the two times are inconsistent, so an error is reported

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

Have

Rendered more hooks
Of course
Rendered fewer hooks
The mistake, we made a modification to the initial code, only changed one place, and the other remained unchanged:

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

To:

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

Similarly, modify our

gender
This one
state
, at this time
react
Will report the following errors to us:

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

Why this time

fewer
Instead of
more
What? Let s take a look at the above knowledge, first of all
Previous render
, Because
if
After the conditions have been modified
if
The statement inside cannot be executed, and it is directly rendered with
Pregnancy period
of
jsx
, Which is rendered this time
4
A
hooks
:

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

And when we modified

gender
This one
state
When the redraw is triggered, at this time
if
The condition was met and executed
if
The statements inside, the following statements are no longer executed, at this time
Next render
Just execute to
if
In the statement
return
, So this time only rendering
3
A
hooks
, Which is at the top
3
A
hooks
:

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

We can write it ourselves

Previous render
with
Next render
, So there will be a more intuitive comparison:

Previous renderNext render
gendergender
isUnder18isUnder18
isOver60isOver60
gestationundefined

In this way, you can intuitively see the two renderings before and after

hooks
Is rendered

Of course, if this requirement is really realized, 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>Under 18:</div> <div> Yes: <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>Under 60 years old:</div> <div> Yes: <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> male: <input type="radio" value={'male'} checked={gender ==='male'} onChange={ e => { const {target} = e; const {value} = target; setGender(value); } } /> </div> <div> Female: <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> Yes: <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 code

gender
state
for
male
(That is, when rendering for the first time):

When we modified

gender
state
for
female
Time:

Then you can happily get the vaccine

Scene in the project

In a project I was responsible for, the network request used

graphql
( apollo-client ), at the same time
ssr
( nextjs ) project, which also needs to obtain the user's geographic location, here is the Baidu map JavaScript API v3.0 class reference _Geolocation , here by the way, Baidu map is really difficult to use, I have used Baidu cloud storage before, and the documentation is incomplete , The use of even guessing, the function is not complete, and I have to change the source code, I am drunk, now I found the previous one when I was writing the article
Positioning demo
Can't find it, just attach
Geolocation
Documentation for this class

The operation of obtaining the user's location is asynchronous, and we need to locate it according to the browser's built-in positioning interface when the program is rendered to the client, that is, the browser. If it fails, we will go.

IP
Positioning, this logical Baidu positioning
API
Help us encapsulate it, we can use it directly, but because it is asynchronous, we need to do this, the process is as follows:

  1. Present a version of the page to the user first
  2. Get user coordinates
  3. After getting the location, request the back-end interface to re-acquire the data
  4. page
    loading
  5. Request end cancellation
    loading
  6. Re-render the page with new data

Page used

function component
, Used at the same time
hooks
, The project has always been ok, until the login and registration process was accessed, the login and registration was written by another partner, and he packaged it into a
js
File, which means this
js
give
window
Added a
object
, Using the traditional way, is to pass the login registration box
div
of
id
Way to use, I need to visit when the program is rendered to the browser
window
To get this packaged by him
object
So as to initialize the login and registration function

Looking at the above process, I need to

6
Do this action after the step, because if you do this operation before getting the coordinates, if the user agrees to get the coordinates, and the coordinates are re-rendered at this time, the login registration box after the initialization will disappear, so the page can only be rendered the second time. Is the first
6
Do it after step:

if(loading) { return <LoadingSpin/>; } useEffect( () => { //Initialize login registration box }, [] ) Copy code

At this time I committed the above

demo
I m wrong in the conditional judgment
return
Used after
hook
, The calling sequence will change at this time, eventually leading to
react
Report an error, but if I change
useEffect
When the user coordinates are obtained, the page is re-rendered, which causes the login and registration initialization to fail. At this time, I thought of a method: Now that the coordinates are obtained, I need to render when I request data
loading
Component, then I'm in
loading
of
componentWillUnmount
It's not enough to do the initialization operation in the life cycle

Just do it, remove the above

if
return
Written later
useEffect
, Amended to read as follows:

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 code

here

LoadingSpin
Is very typical
function component
An example of a component, a pure display component, using
fc
It's perfect, even if you add some
state
, Use
useState
These ones
hooks
It's also the most suitable, here
useEffect
:

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

Its first parameter is a function, the function if

return
A function, then
return
This function will be executed before the component is uninstalled, which is our
class
Component
componentWillUnmount
, Then when used externally, pass it the method of initializing the login registration component:

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

This way it won t break

react
of
hooks
Order, but also met our needs

If you think this article is useful to you, remember to give me a thumbs up, a favorite, and everyone gathers firewood, I hope there is no hard code, no hard to achieve needs