React Hooks
Apr 29 2019 02:29pm | | Share

An Introduction to React Hooks

By Leonard Lacson

React Hooks allow for a new style of writing components that make them easy to read, test, and reuse logic between components.

 

Functional Components are great for clarity and simplicity of code, but don’t have any state (an object that determines how a component behaves). They simply take “props”, which are object arguments with data, and return a React element. Classes, on the other hand, are great for stateful React components, but can get complicated or confusing.  React Hooks allow you to write Functional Components that contain state while keeping the clarity of code.

 

By the end of this article, we’ll understand what React Hooks are, some development problems that they address, and if they’re the right fit for your project!

 

What are Hooks?

 

Hooks give us the power to use local component state and introduce side effects within functional components; this means that we do not have to use class-based components to access stateful features anymore.

 

This new API also gives us a new way to share stateful logic between components through the use of Custom Hooks.

 

Hooks went into alpha in late 2018 and were officially released in February 2019's React 16.8.0 update.

 

Unsurprisingly, Hooks have been very well-received by the React community, even during its early experimental stages.

 

I've Always Used Classes. Are There Any Benefits to Using Functional Components?

 

Personally, I have found functional components a lot easier to read than class-based components, since they are just plain JavaScript functions that do not use local state and lifecycle methods.

 

Functional components allow us to think very carefully about how to structure our React application - the lack of local state and lifecycle methods make it very evident exactly how we should separate our container and presentational components.

 

But Leonard, these benefits are for functional components without local state and lifecycle methods - what if I need those in my component?

 

Up until now, whenever we have started writing a functional component and realized that it required local state or needed to trigger a side effect when the component mounted, updated, or unmounted, we have always had to convert it to a class-based component.

 

However, as of React 16.8.0, we no longer need to do that conversion. This is where we introduce Hooks into our functional component!

 

Why Should We Use Hooks?

 

Using Hooks addresses a lot of problems that React developers have often faced. Some of the more common issues are the following:

 

  1. It was difficult to reuse stateful logic between components.

- React has previously tried to solve this issue with Higher-Order Components (HOCs) and Render Props. However, these patterns had some drawbacks.

 

- HOCs made it difficult to differentiate data coming from the HOC, and data passed from the component.

 

- Render Props, on the other hand, with several levels of nesting, can become unreadable, and as it grows becomes what is often referred to as the "pyramid of doom". This is similar to "callback hell" (http://callbackhell.com/), a term that may be familiar to those who have dealt with Asynchronous JavaScript before.

 

- Using Hooks addresses these problems by letting us create our very own custom hooks to reuse stateful logic across multiple components.

 

  1. Code readability diminished as the component grew in complexity and size.

- In class-based components, there are often multiple lines of unrelated code that are grouped together within a single method (e.g. unrelated code regarding data fetching, event listeners, etc. in componentDidMount() or componentDidUpdate()). Along with constructors, binding event handlers, and having to use the this keyword all over the place, larger components can get messy real fast. (However, we can get around this by using class fields: https://github.com/tc39/proposal-class-fields)

 

- Hooks solves this readability issue by providing some separation of concern. Related code are now grouped together within their own hook, grouping them by side-effect and not by lifecycle method (e.g. via the useEffect() hook).

                                  

Hooks in Action

 

Let us see a simple example showing the use of local component state. This example below is what we are used to in terms of writing stateful React code - through class-based components:

 

import React, { Component } from 'react'

class Example extends Component {

    constructor() {

        super();

        this.state = {

            count: 0

        }

    }

    render() {

        return (

          <div>

            <button onClick={() => this.setState({ count: this.state.count + 1 })}>

              { this.state.count }

            </button>

          </div>

        );

    }

}

 

Now, let us move away from class-based components and create the functional component equivalent of the first example. In the example below, we use Hooks to introduce local component state via the useState() Hook.

 

import React, { useState } from 'react';

 

function Example() {

 

  /*

    The array destructuring syntax used below allows us to give different names to

    the state variables we declared by calling useState. In this case,

    our state variable is `count`.

    We also have the initial state as the argument to useState.

  */

 

  const [count, setCount] = useState(0);

  // useState returns a pair of values - the current state, and a function that updates it.

 

  // Notice that we don't use the `this` keyword and `this.state` anywhere in this component, unlike the previous example.

  return (

    <div>

      <button onClick={() => setCount(count + 1)}>

        { count }

      </button>

    </div>

  );

}

 

Next up, let us do a comparison of side effects in class-based components and functional components using Hooks.

 

This example is how we would typically add and remove event listeners in a class-based component.

 

componentDidMount() {

  window.addEventListener('mousemove', () => {})

}

 

componentWillUnmount() {

  window.removeEventListener('mousemove', () => {})

}

 

The example below, on the other hand, is how we could achieve the same side effects using Hooks within a functional component. We will be using the useEffect() Hook to have both addEventListener() and removeEventListener() grouped together. This is the separation of concerns that I mentioned earlier.

 

useEffect(() => {

  window.addEventListener('mousemove', () => {});

 

  /*

    useEffect comes with a clean-up function which runs when a component unmounts.

    The clean-up function is below, which is returned from the hook.

    useEffect must return either a clean-up function or nothing.

  */

  return () => {

    window.removeEventListener('mousemove', () => {})

  }

}, [])

  /*

    We provide an empty array [] as a second argument to the useEffect hook in order to fire it on component mount, and to avoid activating it on component updates.

  */

 

Aside from useState and useEffect, there are plenty of other Hooks that come straight out of the box. You can find them here: https://reactjs.org/docs/hooks-reference.html.

 

Custom Hooks

 

Once we organize our code better using Hooks and see the separation of concerns that it offers, we begin to realize some common patterns in our application that welcome code reusability.

 

This is where a Custom Hook comes in. It is another JavaScript function that may call other Hooks. When we want to share logic between two JavaScript functions, we just extract it to a third function.

 

Custom Hooks are the Hooks-equivalent of HOCs and Render Props.

 

This section can become its own separate blog post, so in the meantime, you can check out how Custom Hooks work in detail by reading this post by the React Team: https://reactjs.org/docs/hooks-custom.html

 

You can also look at several community-made Custom Hooks that might just fit your application's specific use cases: https://nikgraf.github.io/react-hooks/

 

What Else About Hooks Do We Need to Know?

 

First and foremost, you do not need to use Hooks in your codebase right now, even if you updated to React 16.8.0. Classes are here to stay, so it is good to keep the current state of your codebase intact until your team is comfortable and has fully immersed themselves in the Hooks API, as this could be a major rewrite of your application.

 

If you do decide to jump in, I would recommend that your development team gradually adopts this API by first writing new functional components from scratch with Hooks, and eventually start refactoring existing class-based components once the team gets it going. Since Hooks are completely backwards-compatible, there are no breaking changes and surprises here.

 

However, as with any new major feature, it is best to watch how the community works with the stable releases of React for the next few months. This could show some improvements, new community-made Hooks, and maybe even show the caveats of using Hooks. This allows you and your team to really analyze if using Hooks are a right fit for your application.

 

It is also worth noting that since we are moving away from classes by using Hooks, a decent understanding of how Lexical Scoping and JavaScript Closures work are a good way to demystify Hooks and helps to understand why the "Rules of Hooks" are necessary: https://reactjs.org/docs/hooks-rules.html.

 

An article by Shawn Wang (@swyx) from Netlify dives deep into Closures and how React Hooks work under the hood: https://www.netlify.com/blog/2019/03/11/deep-dive-how-do-react-hooks-really-work/

 

Conclusion

 

Let me be perfectly clear, I believe that there is absolutely nothing wrong if you decide to stay with class-based components. After all, they are here to stay. However, it is good to have an open mind about the newer ways of writing React code, integrating Hooks with new API (i.e. Concurrent Mode and Suspense for Data Fetching), and making sure to explore ways on how to keep your codebase future-proof.

 

When I say future-proof, that does not just mean towards upcoming APIs - it can also mean towards newer developers. For those who have learned React using Hooks, it might be a daunting task to maintain older code that implements class-based components, HOCs, render props, mixins, and the like.

 

One last piece of advice that I can give you on this new API is to not refactor your code for the sake of refactoring. Make sure that you have everything considered - developers, maintenance, requirements, etc. and only jump in when you are fully ready.

 

Hooks open up a whole new way of doing things in the React world, but it does not necessarily mean that it should be the only way to write components moving forward. At the end of the day, it is best to develop React applications in whichever way you, your team, or your company are most comfortable with.

 

References