Getting started with React Hooks

Introduction

React Hooks is a new feature that is likely coming in React 16.7. If you have been on Twitter, you have likely seen the amount of buzz surrounding the announcement of React Hooks. A quick look at Google Trends for the past 30 days, reveals that there has been an increased interest in React hooks.

react-hooks-interest

So what are React Hooks and how can you use them in your React code base? We will consider this in this article.

Prerequisites

To get the most out of this tutorial, you need knowledge of JavaScript and the React framework. If you want to play around with React Hooks, you will need the alpha build of React as this feature is still in alpha (as at the time of writing this article).

What are React Hooks?

When you are creating a React application, you usually have to create components. Components are the building block of React applications. React components can be either stateful or stateless.

When you create a component as a class that extends the React.Component class, you have access to state and React lifecycle methods: componentDidUpdate, componentDidMount, and the others.

This is a typical React component. It has access to state and lifecycle methods:

1import React from 'react';
2    
3    export default class extends React.Component {
4      state = {
5        name: 'Neo Ighodaro'
6      }
7      
8      componentDidUpdate() {
9        // [...]
10        
11        this.setState({ 
12          name: 'Neo Not Ighodaro' 
13        })    
14        
15        // [...]
16      }
17    }

However, when you create stateless components, you don’t have access to state of lifecycle hooks by default.

1import React from 'react'
2    
3    export default () => {
4      return (
5        <div className="island">
6          <h1>Hi, I'm stateless :(</h1>
7        </div>
8      )
9    }

This is where React Hooks come in. React Hooks allows you to access state and lifecycle hooks in a React function component.

Note: React hooks are still in alpha and should probably not be used in a production environment as the API could still change before it reaches the stable build. You can follow the RFC here. You can also look at the documentation and FAQs here.

What about classes in React?

The React team has stated specifically that they do not intend to replace classes as the Facebook team uses them extensively. Hooks are just another arrow in the quiver of tools available. If anything, Hooks can make it easier to break down complex React components as seen in Dan Abramov’s ReactConf 2018 demo code presentation.

With React Hooks, you can extract complex components and break them down into smaller functions that are a lot more testable.

Some in-built React Hooks

Note: At the time of writing, the hooks feature was still in alpha so some of the API might have changed in the stable version of React.

Now that we have talked about what React Hooks are, let’s see some of the Hooks that come bundled with React and how we can use them in our React applications.

The useState hook

Let’s look at a typical React component example:

1import React from "react";
2    import ReactDOM from "react-dom";
3    
4    import "./styles.css";
5    
6    export class DoStuffButton extends React.Component {
7      constructor(props) {
8        super(props);
9        this.state = { buttonText: "Do something" };
10        this.updateButtonText = this.updateButtonText.bind(this);
11      }
12    
13      updateButtonText() {
14        this.setState({ buttonText: "Loading..." });
15        window.setTimeout(
16          () => this.setState({ buttonText: "Do something" }),
17          2000
18        );
19      }
20    
21      render() {
22        return (
23          <button onClick={this.updateButtonText}>{this.state.buttonText}</button>
24        );
25      }
26    }
27    
28    export class App extends React.Component {
29      render() {
30        return (
31          <div className="App">
32            <DoStuffButton />
33          </div>
34        );
35      }
36    }
37    
38    const rootElement = document.getElementById("root");
39    ReactDOM.render(<App />, rootElement);

Edit on CodeSandbox

In the code above, we created a DoStuffButton component that changes the text when clicked using the state. Then we use the App component to render it. Simple stuff. Let’s see though how we can simplify the entire code with React hooks; useState specifically.

1import React, { useState } from "react";
2    import ReactDOM from "react-dom";
3    
4    import "./styles.css";
5    
6    function DoStuffButton() {
7      const [buttonText, setButtonText] = useState("Do something");
8    
9      function updateButtonText() {
10        setButtonText("Loading...");
11        window.setTimeout(() => setButtonText("Do something"), 2000);
12      }
13    
14      return <button onClick={updateButtonText}>{buttonText}</button>;
15    }
16    
17    function App() {
18      return (
19        <div className="App">
20          <DoStuffButton />
21        </div>
22      );
23    }
24    
25    const rootElement = document.getElementById("root");
26    ReactDOM.render(<App />, rootElement);

Edit on CodeSandbox

While the example is not very practical, it does show how you can use the useState hook in a React function component.

The useState function returns an array with two elements. The first item being the current state value and the second being a function used to update the state. Logging the function to the console, we see this:

react-hooks-console

The useEffect hook

You can read more about the useState hook in the documentation here. However, let’s see how useState can be used in code. We will use the same code as we used in the useState example with the slight addition of the componentDidMount lifecycle method:

1import React from "react";
2    import ReactDOM from "react-dom";
3    
4    import "./styles.css";
5    
6    export class DoStuffButton extends React.Component {
7      constructor(props) {
8        super(props);
9        this.state = { buttonText: "Do something" };
10        this.updateButtonText = this.updateButtonText.bind(this);
11      }
12    
13      // Added this...
14      componentDidMount() {
15        this.setState({ buttonText: 'Set this instead'})
16      }
17    
18      updateButtonText() {
19        this.setState({ buttonText: "Loading..." });
20        window.setTimeout(
21          () => this.setState({ buttonText: "Do something" }),
22          2000
23        );
24      }
25    
26      render() {
27        return (
28          <button onClick={this.updateButtonText}>{this.state.buttonText}</button>
29        );
30      }
31    }
32    
33    export class App extends React.Component {
34      render() {
35        return (
36          <div className="App">
37            <DoStuffButton />
38          </div>
39        );
40      }
41    }
42    
43    const rootElement = document.getElementById("root");
44    ReactDOM.render(<App />, rootElement);

Edit in CodeSandbox

Above, we added the componentDidMount lifecycle method to the DoStuffButton component. This method, as you probably know, will be fired when the component is mounted. So how can React hooks provide us this functionality in a React function component? The useEffect hook. Let’s see this in action:

1import React, { useState, useEffect } from "react";
2    import ReactDOM from "react-dom";
3    
4    import "./styles.css";
5    
6    function DoStuffButton() {
7      const [buttonText, setButtonText] = useState("Do something");
8    
9      useEffect(() => setButtonText('Set this instead'))
10    
11      function updateButtonText() {
12        setButtonText("Loading...");
13        window.setTimeout(() => setButtonText("Do something"), 2000);
14      }
15    
16      return <button onClick={updateButtonText}>{buttonText}</button>;
17    }
18    
19    function App() {
20      return (
21        <div className="App">
22          <DoStuffButton />
23        </div>
24      );
25    }
26    
27    const rootElement = document.getElementById("root");
28    ReactDOM.render(<App />, rootElement);

Edit in CodeSandbox

As seen in the example above, we have implemented the useEffect hook and it is running when the DoStuffButton component is mounted.

As said in the docs, “you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.”

Rules of React Hooks

As with most things in life, there are some rules to consider when using hooks in your React application. The rules are as follows:

  • Call Hooks exclusively at the top level of your function. Don’t put them in loops, conditionals, or nested functions.

By following this rule, you ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls. - Rules of Hooks

  • Only call Hooks from function components or custom Hooks. Don’t call them from outside a component.

By following this rule, you ensure that all stateful logic in a component is clearly visible from its source code. - Rules of Hooks

  • When writing custom Hooks, use the use keyword as a prefix to the name of the hook. This makes it easy to follow convention and makes it easy to spot Hooks when looking at the code.

As a side note, there is an ESLint plugin that enforces these rules. To try it, you can run the following command in your terminal inside the project directory:

    $ npm install eslint-plugin-react-hooks@next 
1// Your ESLint configuration
2    {
3      "plugins": [
4        // ...
5        "react-hooks"
6      ],
7      "rules": {
8        // ...
9        "react-hooks/rules-of-hooks": "error"
10      }
11    }

The plugin will be added to the Create React App and other similar toolkits by default in the future.

Creating custom React Hooks

One more exciting thing about Hooks is custom Hooks. You can create custom Hooks that can be used all around your application. This will reduce duplicated code by a lot when used properly. Custom functions are regular JavaScript functions that have a name prefixed with the use keyword and have access to React Hooks.

For instance, we can have a custom hook defined like this:

1// File: FriendStatus.js
2    import { useState } from "react";
3    
4    export function useFriendStatus({ initialState }) {
5      const [friends, setFriends] = useState(initialState);
6    
7      const setOnlineStatus = (id, friend, online) => {
8        if (friend.id === id) friend.online = !!online;
9        return friend;
10      };
11    
12      const setFriendOnline = id => {
13        setFriends(friends.map(friend => setOnlineStatus(id, friend, true)));
14      };
15    
16      const setFriendOffline = id => {
17        setFriends(friends.map(friend => setOnlineStatus(id, friend, false)));
18      };
19    
20      return [friends, { setFriendOnline, setFriendOffline }];
21    }
22    
23    
24    // File: index.js
25    import React, { useState } from "react";
26    import ReactDOM from "react-dom";
27    
28    import { useFriendStatus } from "./FriendStatus";
29    
30    import "./styles.css";
31    
32    function FriendListItem() {
33      const [friends, { setFriendOnline, setFriendOffline }] = useFriendStatus({
34        initialState: [
35          { id: 1, name: "Phoebe", online: false },
36          { id: 2, name: "Rachel", online: false },
37          { id: 3, name: "Ross", online: false }
38        ]
39      });
40    
41      return (
42        <>
43          {friends.map(friend => (
44            <li style={{ color: friend.online ? "green" : "grey" }} key={friend.id}>
45              {friend.name} &nbsp;
46              <button onClick={() => setFriendOnline(friend.id)}>Set Online</button>
47              <button onClick={() => setFriendOffline(friend.id)}>
48                Set Offline
49              </button>
50            </li>
51          ))}
52        </>
53      );
54    }
55    
56    const rootElement = document.getElementById("root");
57    ReactDOM.render(<FriendListItem />, rootElement);

Edit on CodeSandbox

Above we have a simple example of how custom hooks can be used in an application. In the FriendStatus.js we defined the custom hook and in the index.js file, we used the custom hooks and the return value to make up the user interface.

Conclusion

In this tutorial, we have learned how to use the new React Hooks feature and how it can improve the readability and testability of complex React components. The introduction of React Hooks should not be seen as a reason to stop using classes but as a tool in your bag to make even better apps.

The source code to the code snippets in this tutorial is available on GitHub.