Given the following component, when I press down on the age selector and change the value to 15, such that I render a form without the driver license field, I get the error:
Make sure that you didn't run useEffect
conditionally.
For example, if you have some code like the following:
if(condition) {
useEffect(()=>{
doSomething();
}, []);
}
Then change it to
useEffect(()=>{
if(condition) {
doSomething();
}
}, []);
Then the error would not happen.
So I also experienced this error, but it was not related to the hook being wrapped in a condition. Instead it was caused by me having bad key values.
I had a component that moved items from one list to another by dragging and dropping. However, some of those items were locked, so I simply removed the reference to my hook in those elements.
The issue was that I used the index as a key, thus when I dragged a new element on top it got the same key as the locked element, the value of ref was changed and React complained with the above error.
So my answer to those who come across this issue is - check whether you have truly unique keys!
The problem is that in the first render, 3 useState
hooks were invoked - name
, age
and license
but after the age is changed to a value below 16, the useState
for license
is no longer invoked, resulting in only the first 2 hooks being invoked. As the 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
anduseEffect
calls.
The order of the hooks being called is important, and if we write code that causes hooks to not be called, React will not be able to match the hook call with its values.
The solution is to move the license
hook up to the top of the function so that it gets called regardless whether it is needed or not.
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>
I have resolved this issue by changing the order of React Hooks.
So I have changed code from
function(){
const [loading ,setLoading] = React.useState(false);
if(loading){
return "loading ....."
}
useEffect(()=>{
if(condition) {
doSomething();
}
}, []);
return <div>component</div>
}
to
function(){
const [loading ,setLoading] = React.useState(false);
useEffect(()=>{
if(condition) {
doSomething();
}
}, []);
if(loading){
return "loading ....."
}
return <div>component</div>
}
So , when loading becomes true then useEffect will not run and that will throw error..but if i declare loading JSX element after useEffect then it will work perfect.