Back

Understanding React hooks

Understanding React hooks

React is one of, if not the most well-liked JavaScript frameworks. An attribute called Hooks is part of React. Due to their complexity, React hooks might be confusing for beginners, who thus become stuck when they encounter them. In this article, we will discuss that.

What are React hooks?

React hooks are JavaScript functions that isolate the reusable part from a functional component. Hooks are React APIs added to React 16.8. They made it possible for features previously only used in React class components to come into React functional components. They are functions that bring the power of React class components to functional components. Hooks allow us to separate our code, manage side effects, and more.

Origin of React hooks

React functional and class components had different purposes before hooks. The only time functional components were utilized was to render data to the user interface (UI). Only parent components, which were class, could receive and render props for them. Functional components were unaware of the lifecycle and did not maintain an internal state.

Contrarily, class components keep track of a component’s internal state and provide lifecycle methods that let you do activities during each stage. For instance, after a component mounts, you can update the state due to user interaction and fetch data from an external API. A class component keeps track of its internal state and lifetime, making all of this feasible. The term “smart components” was used to describe class components and is still used today.

Hooks were added to solve some of the problems related to using React class components, but some are not directly connected to React but, rather, the way native JavaScript classes are designed.

React hooks have a few rules, including:

  • Hooks can only be called inside React function components.
  • Hooks can only be called at the top level of a component.
  • Hooks cannot be conditional

These rules are crucial; breaking them could make your hooks stop working.

Types of React hook

Above are the types of React hooks we have, but we will be digesting the first four (4) out of the fifteen we have listed because those are the ones we are likely to use when working on a project.

useState

In some projects (websites, web apps, etc.), we need them to behave differently when the user inputs different values or opt-in for different options. useState allows us to achieve that.

The React useState hook lets us keep track of the state while using the function component.

“State,” according to Oxford Languages, is the particular condition someone or something is in at a specific time. In React, state refers to the value of a component’s property at a specific time.

By using useState, we know when these values have changed from their initial state to another, and we can update our components according to those changes.

Let’s write some code to implement the useState using a functional component named myFavouriteFood.

import { useState } from "react";

function myFavouriteFood() {
  const [food, setFood] = useState("fried rice");

  return <h1> My favorite Food is {food}!</h1>;
}

We first imported the useState into our module in the code above because we cannot use it unless we first import it into our module. useState is defined before the return keyword; this is to allow them to initialize before the function returns any value, as we can see in our code. We initialize our useState at line 4 before returning a value at line 6. Some of us might need clarification about what we did in line 4. We destructured the useState to get the return value. The useState function returns a list with two values as its output. The first value is the actual state we will be using, while the second is a function we will use to update this state. By destructuring them, we now get their values. The first value is assigned to the variable food, while the second is assigned to the variable setFood.

Updating our state

How do we update the value of food based on the user’s activity on the web app? To explain this, let’s say we have the multiple choice option and want to return different food as the user’s favorite when they choose one out of the multiple choices we provided.

Let’s update our code to add multiple choices.

import { useState } from "react";

function myFavouriteFood() {
  const [food, setFood] = useState("");
  const updateFood = (e) => setFood(e.target.value);

  return (
    <>
      <h1> Select your favourite food </h1>
      <input
        type="radio"
        id="Fried rice"
        name="fav food"
        onClick={updateFood}
        value="Fried rice"
      />
      <label for="Fried rice">Fried rice</label>
      <br />
      <input
        type="radio"
        id="Jollof rice"
        name="fav food"
        onClick={updateFood}
        value="Jollof rice"
      />
      <label for="Jollof rice">Jollof rice</label>
      <br />
      <input
        type="radio"
        id="fufu"
        name="fav food"
        onClick={updateFood}
        value="Fufu and Egusi soup"
      />
      <label for="fufu">Fufu and Egusi soup</label>
      <br />
      <p> My favorite Food is {food}!</p>
    </>
  );
}

In our code, we updated the value of food when the user clicked on a particular option using events. We achieved this by passing a callback function to the onClick property. Inside the callback function, the updateFood function, we updated the variable food by calling the setFood function and passing the value of the option the user clicked as an argument. Take note that the second value that useState returns, which is setFood in our case, takes in one argument: the updated value of our state. To understand more about JavaScript events, try reading this article All About JavaScript Events.

Although updating strings, integers, and boolean in useState is simple, updating objects and arrays in useState can be difficult due to how they behave. In React, we are not allowed to mutate the values inside the useState. What I’m trying to say is that the values that useState holds shouldn’t be modified; instead, when useState is updated, the values that are supplied to it should point to a new object, as is the default behavior of integer, string, and boolean. As a result, they are easier to update than arrays and objects in useState. Updating objects and arrays in useState, we must create a duplicate or new value that points to a new object.

Updating arrays in State

We use the spread method ... to update arrays in the state. Depending on what we want to accomplish, we can also utilize the concat, map, filter, and slice methods. We use these methods to update arrays in the state since they return an array pointing to a new object. But in this instance, we’ll employ the spread strategy.

import { useState } from "react";

function myFavouriteFood() {
  // Array as the initial value of useState
  const [foodClicked, setFoodClicked] = useState([]);

  const updateFood = (e) => {
    // Updating arrays
    const newFoodClicked = [...foodClicked, e.target.value];
    setFoodClicked(newFoodClicked);
  };

  return (
    <>
      <h1> Select your favourite food </h1>
      <input
        type="radio"
        id="FR"
        name="fav food"
        onClick={updateFood}
        value="Fried rice"
      />
      <label for="FR">Fried rice</label>
      <br />
      <input
        type="radio"
        id="JR"
        name="fav food"
        onClick={updateFood}
        value="Jollof rice"
      />
      <label for="JR">Jollof rice</label>
      <br />
      <input
        type="radio"
        id="fufu"
        name="fav food"
        onClick={updateFood}
        value="Fufu and Egusi soup"
      />
      <label for="fufu">Fufu and Egusi soup</label>
      <br />
      <hr />
      <p> The list of Foods you have clicked are!</p>
      {foodClicked.map((item) => (
        <p>{item}</p>
      ))}
    </>
  );
}

Try running this code and see the output. You are expected to see the foods you have clicked.

Updating objects in State

The spread method ... is used to update objects in State. The concat, map, and other methods we have stated in the array are not applicable because objects don’t support them.

import { useState } from "react";

function myFavouriteFood() {
  // Object as the initial value of useState
  const [foodData, setFoodData] = useState({
    "Fried rice": 0,
    "Jollof rice": 0,
    "Fufu and Egusi soup": 0,
  });
  const updateFood = (e) => {
    // Updating objects
    foodData[e.target.value]++;
    setFoodData({ ...foodData });
  };

  return (
    <>
      <h1> Select your favourite food </h1>
      <input
        type="radio"
        id="FR"
        name="fav food"
        onClick={updateFood}
        value="Fried rice"
      />
      <label for="FR">Fried rice</label>
      <br />
      <input
        type="radio"
        id="JR"
        name="fav food"
        onClick={updateFood}
        value="Jollof rice"
      />
      <label for="JR">Jollof rice</label>
      <br />
      <input
        type="radio"
        id="fufu"
        name="fav food"
        onClick={updateFood}
        value="Fufu and Egusi soup"
      />
      <label for="fufu">Fufu and Egusi soup</label>
      <br />
      <hr />
      <p>
        {" "}
        Foods and times you have clicked them:{" "}
        {Object.keys(foodData).map((item) => (
          <p>{`${item}: ${foodData[item]}`}</p>
        ))}
      </p>
    </>
  );
}

Try running this code and see the output. You are expected to see the foods you have clicked and the number of times you have clicked them.

Session Replay for Developers

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — an open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

useEffect

As its name suggests, the useEffect hook allows you to perform side effects in your components. It enables you to synchronize a component with an external system. In this scenario, external systems are those systems that are not managed by React, such as some browser APIs or third-party libraries.

How useEffect is triggered in React depends on its dependencies array.useEffect accepts two arguments. The second argument is optional.

useEffect(<function>, <dependency array>)
// second argument is optional.

The first argument passed to the useEffect function is a callback function, and the second is an array popularly called a dependency array. A dependency array contains the values that your first argument (function) depends on. To use useEffect, we need to import it into our module and initialize it.

import { useEffect, useState } from "react";
import fetchPost from "./post";

function PostDetails() {
  const [post, setPost] = useState([]);

  useEffect(() => {
    const get_post = fetchPost({ details });
    setPost(get_post);
  });
}

Our useEffect runs after our components are rendered. When our component is rendered, the post is fetched from the API, and we update the post variable (state) with its value when it returns a value. If we run this code, we will figure out that the useEffect is triggered continuously without stopping, making our component keep re-rendering. We might not notice that our component is being re-rendered because it shows the same content. But we can see the browser continuously fetching the post when we move to the network tab after right-clicking and clicking on inspect.

To prevent this, we need to add the dependency array. As I said, how useEffect is triggered in React depends on its dependency array. The dependency array tells React either to re-run a useEffect or not. The dependency array can be filled, empty, or not specified, and the state it is in makes it behave differently. How does this work?

Filled

If a dependency array is specified or filled, React re-runs the useEffect whenever any of the values inside the dependency array changes.

import { useEffect, useState } from "react";
import fetchPost from "./post";

function PostDetails() {
  const [post, setPost] = useState([]);
  const [clicked, setClicked] = useState(false);

  useEffect(() => {
    const get_post = fetchPost({ details });
    setPost(get_post);
  }, [clicked]); // Filled array.
}

When the value clicked is changed, React re-run the useEffect and re-renders the component.

Empty

This is when we provide the dependency array without adding any value inside the array. This tells React to run the useEffect when the component is rendered for the first time only. The useEffect runs only ones which are when the component renders the first time.

import { useEffect, useState } from "react";
import fetchPost from "./post";

function PostDetails() {
  const [post, setPost] = useState([]);

  useEffect(() => {
    const get_post = fetchPost({ details });
    setPost(get_post);
  }, []); // Empty array.
}

Not specified

This is when the dependency array is not specified at all. It tells React to re-run the useEffect whenever the component re-renders.

import { useEffect, useState } from "react";
import fetchPost from "./post";

function PostDetails() {
  const [post, setPost] = useState([]);

  useEffect(() => {
    const get_post = fetchPost({ details });
    setPost(get_post);
  }); // No array.
}

React will continue to re-run this useEffect whenever the component re-renders. If you are not careful when using your useEffect this way, you might end up defining a useEffect that keeps re-running. How I address this issue is by setting a condition the function inside the useEffect must pass before it can run.

useCallback

The React useCallback Hook returns a memoized callback function.

Memoization, according to Wikipedia In computing, is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again. This is what the React useCallback hook does, but instead of caching the value, the useCallback hook caches the function itself. React when re-rendering a component recreates all of its functions. This automatically decreases performance. useCallback addresses this issue by telling React whether to recreate its function or not when re-rendering a component.

This allows us to isolate resource-intensive functions so that they will not automatically run on every render. useCallback accepts two arguments, just like useEffect; the first is a function, while the second is an array known as a dependency array.

useCallback(<function>, <dependency array>)

The useCallback hook only runs when one of its dependencies updates.

import { useState, useCallback } from "react";

function App() {
  const [count, setCount] = useState(0);
  const [todos, setTodos] = useState([]);

  const increment = () => {
    setCount((c) => (c += 1));
  };

  const addTodo = useCallback(() => {
    setTodos((t) => [...t, "New Todo"]);
  }, [todos]);

  return (
    <div>
      <p> Todos {todos} </p>
      <p> Count: {count}</p>
      <button style={{ marginRight: "20px" }} onClick={addTodo}>
        {" "}
        add Todo
      </button>
      <button onClick={increment}>+</button>
    </div>
  );
}

When we click the + button, the useCallback doesn’t run because its dependency todos was not changed. The useCallback will run when we click on the add Todo button, which updates the todos state.

useMemo

The React useMemo Hook returns a memoized value.

The useMemo hook is quite similar to the useCallback hook; however, instead of caching functions as in the case of useCallback, we cache and return the outcome of an expensive calculation in useMemo.

You can prevent expensive, resource-intensive functions from running unnecessarily by using the useMemo Hook. Like useCallback, useMemo also takes two arguments: the first is a function, and the second is an array called a dependency array.

useMemo(<function>, <dependency array>)

Defining an expensive function inside a component will decrease the performance of the component.

import { useState, useCallback } from "react";

function App() {
  const [count, setCount] = useState(0);
  const [todos, setTodos] = useState([]);

  const increment = () => {
    setCount((c) => (c += 1));
  };

  function expensiveCalculation(int) {
    for (let i = 0; i < 1000000000; i++) {
      int += 1;
    }
    return int;
  }

  const calculate = expensiveCalculation(count);

  const addTodo = useCallback(() => {
    setTodos((t) => [...t, "New Todo"]);
  }, [todos]);

  return (
    <div>
      <p> Todos {todos} </p>
      <p> Count: {count}</p>
      <button style={{ marginRight: "20px" }} onClick={addTodo}>
        {" "}
        add Todo
      </button>
      <button onClick={increment}>+</button>
      <hr />
      <p>Expensive calculation</p>
      <span>{calculate}</span>
    </div>
  );
}

It will take some time for the component to update when we run this code and hit the add Todo or + buttons. This demonstrates how this expensive function keeps executing during every re-render, lowering the component’s performance.

However, applying useMemo will prevent our component from acting this way.

import { useState, useMemo, useCallback } from "react";

function App() {
  const [count, setCount] = useState(0);
  const [todos, setTodos] = useState([]);

  const increment = () => {
    setCount((c) => (c += 1));
  };

  function expensiveCalculation(int) {
    for (let i = 0; i < 1000000000; i++) {
      int += 1;
    }
    return int;
  }

  const calculate = useMemo(() => expensiveCalculation(count), [count]);

  const addTodo = useCallback(() => {
    setTodos((t) => [...t, "New Todo"]);
  }, [todos]);

  return (
    <div>
      <p> Todos {todos} </p>
      <p> Count: {count}</p>
      <button style={{ marginRight: "20px" }} onClick={addTodo}>
        {" "}
        add Todo
      </button>
      <button onClick={increment}>+</button>
      <hr />
      <p>Expensive calculation</p>
      <span>{calculate}</span>
    </div>
  );
}

This time, when we run this code, clicking the + button causes our component to update gradually, but clicking the add Todo button causes it to update instantly. This is so because, like useCallback, the useMemo hook only executes when one of its dependencies changes, in this case, only when the + button is clicked to update the count variable.

Conclusion

We understand that React hooks are simply JavaScript functions. It allows us to implement some functionality that was only in the React class components in the React functional components. We also saw that the useEffect hook’s re-rendering depends on its dependency array, useCallback and useMemo hooks will only run when any value in their dependency array changes, and useState helps us keep track of our component state.

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs and track user frustrations. Get complete visibility into your frontend with OpenReplay, the most advanced open-source session replay tool for developers.

OpenReplay