linkedin Skip to Main Content
Just announced! We now support spreadsheets
Back to blog

Rules of React Hooks

Development

Over the years, React has become one of the most used and loved JavaScript frameworks in the world. 

React is a component-based library with two standard ways of defining its components: 

  1. As class components 
  2. As functional components 

You can use these two methods to achieve the same outcome but with different syntax.

The class components are ES6 classes that return JSX and necessitate a React class extension, while the functional components are ES6 functions that return a React element as JSX. It is essential to know that they both return JSX (JavaScript XML), allowing us to write HTML elements in JavaScript. React then takes this JSX and places the elements in the DOM.

A comparison between class based and functional component returning JSX with a heading of "Hello World"

Before the introduction of React Hooks in version 16.8, the class component had always been the superior method of creating a component when you need to work with state (data management) and handle lifecycle methods, like component mounts, renders, updates, and unmounts. 

In those days, you could only use the functional component to render a user interface. With the introduction of Hooks in 2019, functional components gained the superpowers that class components already possessed, which meant that you could work with state and handle lifecycle methods.

What are React Hooks?

React Hooks help us Hook into React features in functional components. These features include state and side effects (similar to lifecycle methods). 

The two most common React Hooks are useState() and useEffect(). The useState() Hook acts as a store that allows it to use state variables and update them. You can even destructure the useState() Hook to hold the state variable and update function separately. It also allows you to pass an initial value directly into the Hook.

A comparison of how the class and functional components work with states and output state values within JSX.

With the useEffect() Hook, you can efficiently perform side effects (similar to lifecycle methods) such as when your application mounts/renders or when a value updates. You can read about useEffect(), and its rules here.

Understanding the rules that guide the operation of React Hooks will help you and your team avoid some unnecessary errors and bugs.

The two rules of React Hooks

Two significant rules need to be followed when working with Hooks. These rules are essential to maintain order, avoid unnecessary bugs, and help us write clean code. These rules are:

  1. Only call Hooks at the top level
  2. Only call Hooks from React functional components or other Hooks

Only call Hooks at the top level of your functional component

The top level of a functional component is the base of your function body before you return your JSX elements. This is where you can call all your Hooks, so React can preserve and take note of the order in which these Hooks are called.

A display of the top level of functional components and an example of the type of Hooks that can go into it.

Calling your Hooks at the top of the function means you must not be calling them within loops, conditions, and nested functions.

import { useState, useEffect } from 'react';

const App = () => {
    // ✅ - correct
    const [todos, setTodos] = useState([]);
   
    // ❌ - Breaks the call order
    if (todos) {
        const [count, setSetcount] = useState(todos.length);
    }

    // ❌ - Breaks the call order
    todos.forEach(todo => {
        useEffect(() => {
            console.log(todos);
        });
    });
   
    // ✅ - correct
    useEffect(() => {
        console.log(todos);
    });

    return (
        // ...
    );
};

export default App;Code language: JavaScript (javascript)

It is necessary to call Hooks at the top level of your functional component to ensure that every time your component renders, these Hooks are called in the same order. The call order is essential for Hooks to work correctly.

For example, when you consider the code above, it uses the value of the todos state to decide whether to call the second and third Hook. When the condition fails, or something happens to the loop, it breaks the order. React will then have a hard time figuring out how to preserve the state of your component.

This is what React does when you make use of Hooks. It identifies the order in which the Hooks are used at the initial render, then in subsequent renders, React will be able to preserve the state of your component.

A perfect example to explain this would be this: you have a functional component with a state that holds the user’s data, such as the firstName and age. Then you get the lastName from another state. You now use the useEffect() Hook to update your page title to reflect both names.

const App = () => {
    // 1. Use the user state variable
    const [user, setUser] = useState({
        firstName: 'John',
        age: 20,
    });

    // 2. Use the lastName state variable
    const [lastName, setLastName] = useState('Doe');

    // 3. Use an effect for updating the user details and updating the title
    useEffect(() => {
        document.title = user.firstName + ' ' + lastName;
    });

    // ...

};Code language: JavaScript (javascript)

This example will always work because the order is constant, and nothing affects the order on every render:

// First render
// ---
// 1. Initialize the name state variable with an object containing name and age
useState({ firstName: 'John', age: 20 })
// 2. Initialize the lastName state variable with 'Doe'
useState('Doe')
// 3. Add an effect to update the title
useEffect()

// Second render
// ---
// 1. Read the user state variable (the argument is ignored)
useState({ firstName: 'John', age: 20 })
// 2. Read the lastName state variable (the argument is ignored)
useState('Doe')
// 3. Replace the effect for updating the title
useEffect()Code language: JavaScript (javascript)

But in a situation where you put one of these Hooks in a condition, the order will break when the condition returns false, leading to bugs:

❌ - Breaks the call order
if(user.firstName){
    const [lastName, setLastName] = useState('Doe');
}Code language: JavaScript (javascript)

The condition will be true on the first render, making React store the Hook’s order. If it returns false in the next render, it then affects any other Hook declared after this one and returns the wrong state for that Hook.

If you must have a condition, then you can take this Hook out of the condition statement and use the state update function (setLastName() in our example) instead:

const [lastName, setLastName] = useState('');

if(users.firstName && !lastName){
    setLastName('Doe');
}Code language: JavaScript (javascript)

If it is a useEffect() Hook, you can write your conditions or nested functions or loops within the Hook itself:

useEffect(() => {
    if (condition) {
    // statement
    }
});Code language: JavaScript (javascript)

Only call Hooks from React functions

A second rule is that Hooks should only be used and called in React functional components or custom Hooks, not regular JavaScript functions or React class components.

The example below is invalid because it is a standard JavaScript function. Even if you decided to import useState(), it won’t work because it’s not a React component or custom Hook.

import { useState } = "react";

function getName() {
    const [name, setName] = useState("John Doe");
    return name;
}
document.getElementById("user-name").innerHTML = getName();Code language: JavaScript (javascript)

To create a custom Hook, you simply create a JavaScript function where the function’s name begins with use. From there, you can use it to call other Hooks. For example, here’s an implementation of the custom Hook useMyName:

export default function useMyName(name) {
    const [state, setState] = useState(value);
   
    useEffect(() => {
    // ...
    });
   
    return anyThing;
}Code language: JavaScript (javascript)

You can learn more about custom Hooks and how to create yours in the official React documentation.

Enforcing Rules With ESLint Plug-in 

Knowing these rules is not enough; you must enforce and abide by them too. It is easy to follow these rules when working alone on a project, but when working in a team or on an external project, it is a good idea to devise ways to check if these rules are followed to reduce the chance of introducing bugs into the code.

React released an ESLint plugin called eslint-plugin-react-hooks to help enforce both React Hook rules. If you want to try it out, you can do so by installing it with the following command:

npm install eslint-plugin-react-hooks --save-dev
// Your ESLint configuration
{
    "plugins": [
    // ...
    "react-hooks"
    ],
    "rules": {
    // ...
    "react-hooks/rules-of-hooks": "error", // Checks rules of Hooks
    "react-hooks/exhaustive-deps": "warn" // Checks effect dependencies
    }
}Code language: JSON / JSON with Comments (json)

ℹ️ If you installed React via Create React App, then this plugin is installed by default, and you will notice it will always flag situations where you don’t abide by the rules with errors like this:

An error that appears when you break one of the rules. This occurs when you make use of the React Hooks eslint plugin.

Try it out for yourself!

You can take a turn implementing React Hook rules for yourself:

Wrapping up

In this article, you have learned what Hooks are, why and when you should use Hooks, and what they can do. You’ve also learned that the order in which your Hooks are called is vital to React. 

You should always call your Hooks at the top level of your components. Furthermore, you can use these Hooks only in React functional components and callback Hooks, not in JavaScript functions.

Thanks for reading, and have fun coding!

I’m Joel Olawanle, a front-end developer and technical writer interested in making the web accessible to everyone by always looking for ways to give back to the tech community. Follow me and connect with me on Twitter.