useEffect.dev
← Back to lessons

Lesson 4. What are hooks anyway? 4. Rules for hooks

You noticed that each call to a hook was referred to by an index when rendering a component several times. The first hook, then the second, etc. It might seem weird, but React has its reasons for this behavior. And what is more important is the consequence it has.

Since each hook call is referred to by its index, this index must remain consistent from a rendering to the next one. If the first hook is a useState storing the name at the first rendering, it cannot be another state storing the user ID at the second one, nor can it be a useEffect.

It implies that you cannot use hooks in conditions, loops, or any function body.

if (id === 0) {
// Using a hook inside a condition is forbidden!
useEffect(() => alert('Wrong ID'), [id])
}
const getUserName = (id) => {
// Using a hook inside a function is forbidden!
useEffect(() => {
fetch(...)
}, [id])
}

It is also not possible to return something prematurly before a hook call:

const Division = ({ numerator, denominator }) => {
if (denominator === 0) return <p>Invalid denominator</p>
// Using a hook after a `return` is forbidden.
const [result, setResult] = useState(undefined)
useEffect(() => {
setResult(numerator / denominator)
}, [numerator, denominator])
return <p>Result = {result}</p>
}

We can simplify the rules on hooks this way: we must do all calls to hooks at the root of the component function body and before any return.

You may think of it as a contraint, but in most cases it is not that hard to find another way. For instance, instead of having a useEffect inside a if, you can put the if inside the useEffect:

useEffect(() => {
if (id === 0) {
alert('Wrong ID')
}
}, [id])

To avoid calling hooks after a return, you may have to use some tricks.

const Division = ({ numerator, denominator }) => {
const [result, setResult] = useState(undefined)
const [invalid, setInvalid] = useState(false)
useEffect(() => {
if (denominator === 0) {
setInvalid(true)
setResult(undefined)
} else {
setInvalid(false)
setResult(numerator / denominator)
}
}, [numerator, denominator])
if (invalid) {
return <p>Invalid denominator</p>
} else {
return <p>Result = {result}</p>
}
}