One of the first blog posts was ”A Practical Introduction to React Hooks”. The React team had just released hooks, and I was so excited about them.
It’s been three years, and when I’m talking to someone new to React, they have no idea what life was like before hooks. To them, hooks can seem needlessly complicated (especially with frameworks like Svelte giving more intuitive and succinct solutions to the same problems).
I’ve found it useful to take a step back and ask the question, “why do we need React hooks?” The answer makes it easier to understand when to use them and how to avoid the “gotchas” associated with them.
In this post, we’ll look at the problem hooks are trying to solve. We’ll then use this knowledge to see what the hooks do and when to use them
Class Components: In The Beginning…
Before React hooks, if you wanted to do anything more than render a simple view, you had to use class components. An example of one looks like this:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = { date: new Date() };
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
This may look quite strange if you’ve never seen a class component before, but for this post, I want to focus on one thing:
Everything inside the class component is created when the component is created and persists until the component is destroyed.
When the component re-renders, it runs the render
method (and the relevant lifecycle methods) but the rest of the code inside the component (the state object, the properties, the methods) aren’t affected.
They’re just there, chilling.
Now let’s see what happens when a function component re-renders.
Function Components
A function component is equivalent to the render
method of a class component. It’s a function that is run every time the component re-renders.
Originally, function components were considered pure functions - they take an input (props) and product an output (JSX). There were no side effects and passing in the same input would result in the same output every time.
That meant when React called a function component, the component would run the function, return the JSX, and then throw everything away
Do you see the problem?
- We have no way of persisting state and other variables.
- All our functions and objects get recreated from scratch.
- We have no way of knowing if the component is on its first render or its 17th.
- We have no way of running any clean up when the component unmounts.
- We have no way of knowing what, if anything, has changed since our last render.
So how do we solve these problems?
We need a place outside our components to store these things, which our components can hook into.
That’s exactly what React hooks allow us to do.
Introducing React Hooks
Now it’s a lot easier to see the purpose behind the hooks:
useState
is a variable that persists across renders and, when it is updated, triggers the component to re-render.useRef
is a variable that persists across renders (but doesn’t trigger a re-render when updated).useEffect
allows us to conditionally run code on certain renders but not all. It’s most commonly used to sync values or to infer that an event has just happened.useMemo
allows us to persist a value. Normally used for objects (or arrays) to prevent expensive code from needlessly running.useCallback
allows us to persist the value of a function, similar touseMemo
.
Understanding when to use them
Understanding the purposes behind the hooks helps us use them for their intended purpose.
For example, it can be hard to know what needs to be included in a useEffect
. What is a side effect?
With our new definitions in mind, we can see that a side effect is anything that isn’t directly involved with rendering the component’s UI. In other words, it is code that doesn’t need to run on every render.
Another common mistake is using useState
instead of useRef
. If we want to persist a value in React, our first question should be, “when this value updates, should it trigger a re-render?”
Once we understand hooks, we can take advantage of the power they give us and use them to their full potential.