18 April, 2022 | 4 min read

The Why Behind React Hooks

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).

Screenshot of JavaScript Rising Stars React category

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.

Only the render function runs when a class component re-renders

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.

Everything is re-run when a function 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.

React throws away local state after each render of a function component (without hooks)

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 to useMemo.

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.


Follow me on Twitter or Bluesky to get updates on new posts.