Use React Hooks Correctly with These Two Rules
Chris Achard
—August 13, 2019
Hooks were introduced at React Conf 2018, and have been steadily gaining popularity as an easy way to introduce state to functional react components.
It's important to note that you don't have to use hooks if you don't want to - but if you do, make sure you follow these two rules of hooks! Otherwise - bad things might happen.
Why Hooks Were Created
First, let's take a look at the problem that hooks are trying to solve.
Here's a React class component with a single bit of state called count
, that counts up when the button is clicked:
// OLD WAY: Class Componentsclass Counter extends React.Component {state = {count: 0}render() {return (<div><buttononClick={() =>this.setState({ count: this.state.count + 1 })}>+1</button><p>Count: {this.state.count}</p></div>)}}
That works just fine, but there are a few parts that can easily create bugs.
Problem 1: What is the meaning of this
?
One of the most confusing parts of JavaScript for new developers is that the this
keyword changes meaning based on the context. This includes the very important, but (seemingly) arbitrary way that you define your functions.
For example, this function:
// probably not what you wantfunction onClick() {this.setState({ this.state.count: count + 1 })}
doesn't bind this
to the class component - and so probably won't behave like you want! Instead, you have to remember to either bind that function to the class - or use an arrow function:
// probably what you wantconst onClick = () => {this.setState({ this.state.count: count + 1 })}
Until you get a really good feeling for what the meaning of this
is in various parts of a class component, it can cause subtle, and really confusing bugs.
Hooks simplify that by removing the need to track the meaning of this
altogether. This is because there is no class that you have to reference (since everything is functional components with hooks).
Problem 2: Using this.state...
to access data and calling this.setState
to change it
The fact that state
lives on the class component means that anytime you want to access a value in state, you have to preface it with this.state
. This can be confusing to beginners - but also can bite experience programmers.
As a testament to how annoying it can be - when creating the demo for this article, I originally typed the following onClick function:
// Can you spot the bug?...this.setState({ count: count + 1 })...
Do you see the bug? Yep; I forgot to say this.state.count:
instead of just count:
in the setState
call. That didn't cause a render error or anything - but it just didn't work; and it took me some debugging to figure out what went wrong... annoying!
Hooks simplify that by removing the concept of class state
, and just giving access to the values and set functions directly. No more this.state
!
Problem 3: People are using functional components more and more
Since functional components are 'just functions', and are generally easier to type and to reason about - more and more people are defaulting to functional components over class components.
The problem is, that as soon as you want to add state to a functional component - you have to convert it to a class component, or bring in a more complicated library like Redux.
Hooks simplify that by giving you a way to add state to functional components directly with the useState
hook.
Then - with the useEffect
hook, you can replicate the lifecycle methods of class components, and suddenly - you don't need classes anymore!
So you can just keep using your functional components everywhere now.
How Hooks Work
Ok - let's take a look at the same Counter
example; but this time with hooks:
// NEW WAY: Hooksimport React, { useState } from "react";...const Counter = () => {const [count, setCount] = useState(0)return (<div><button onClick={() => setCount(count + 1)}>+1</button><p>Count: {count}</p></div>)}
Instead of setting initial state with state = {}
, we use the useState
hook to define a new bit of state that we call count
, which defaults to 0
.
Importantly (for the rules later) - React stores this value internally in an array of all the values created with hooks in this function. We only have one here; but let's add another to see what that looks like:
// Multiple statesconst Counter = () => {const [count, setCount] = useState(0)const [name, setName] = useState('Chris')...}
So with two useState
calls above, we know that React is storing an array of two values.
OK! Now we have all the information we need in order to understand the rules of hooks.
The Rules of Hooks
Rule 1: Call hooks unconditionally at the top level of your component
Because hooks are stored in an array, the order that they get called matters. Since React components are re-rendered each time data changes, that means that the exact same hooks must be called in the exact same order on every single render.
That means if you added an if
statement in there:
// DON'T DO THIS!...if(myBool) {const [count, setCount] = useState(0)}const [name, setName] = useState('Chris')...
The count
state would sometimes be created - and sometimes wouldn't be. But React can't track that internally - so it would mess up the value of all the state stored in the array for that component.
Also - don't put hooks inside of loops (or any other control function):
// ALSO DON'T DO THIS!...for(my conditions) {...useState......useEffect...}...
... because if your conditions are based on variables that change, then you'll have hooks run in different orders from render to render.
Rule 2: Only call hooks in React functions or custom hooks
This rule exists for the same reason at rule #1, but is slightly different.
For example, if we created a helper function called doSomething
:
// DON'T DO THISfunction doSomething() {const [count, setCount] = useState(0)... do something with count ...}
Then you (or another developer) may not realize that that doSomething
function actually calls a hook - and may be tempted to call doSomething
out of order:
// MISUSE of doSomethingconst Counter = () => {return <button onClick={doSomething}>Do it!</button>}
Which breaks the hooks!
It breaks them because React internally can't keep track of hooks that run out of order (same as rule #1) - so only use hooks at the top of react components, or in custom hooks that you create.
Hooks Aren't Scary
Hooks solve a few common problems that developers have had with React class components. You don't have to use them (so don't go out and replace a bunch of your code 'just because') - but if you do, then follow the rules:
- Call hooks unconditionally at the top level of your component
- Only call hooks in React functions or custom hooks
And that's it!
Once you know that React keeps hook values in arrays, then it makes sense: don't change the order of the hooks that are called, or else React can't keep track of what's what!