React Hooks have revolutionised the way we write React applications. Introduced in React 16.8. They provide a more concise and readable way to manage component state, side effects, and logic. In this article, we will dive deep into React Hooks, exploring their usage with practical examples.

What are React Hooks?

React Hooks are functions that let you “hook into” React state and lifecycle features from function components. Before Hooks, you could only use these features in class components.

Imagine building a simple counter app. You want to track the count and display it, but previously only class components could manage state. With Hooks, you can use useState in your function component to store the count, while useEffect hook to update the DOM with the new count after each increment. No class boilerplate needed! This keeps your code cleaner, simpler, and focused on the functionality, making Hook-powered function components ideal for smaller, reusable UI elements like this counter.

Basic Hooks

We look into the most popular hooks in React like useState, useEffect, useContext, useRef, useReducer, useMemo and useCallback. Though we can create our own custom hooks by utilizing some of the mentioned.

useState

The useState hook allows you to add state to your functional components. It returns an array with two elements: the current state and a function to update it.

import React, { useState } from ‘react’;

function Counter() {

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

  return (

    <div>

      <p>Count: {count}</p>

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

    </div>

  );

}

Here we implement a simple counter feature. It uses the useState hook to declare a state variable count initialized to 0. The component renders the current value of count inside a paragraph element and a button labeled “Increment.” When the “Increment” button is clicked, the setCount function is called with a new value of count + 1, which updates the count state variable and triggers a re-render of the component. The paragraph element then displays the updated value of count. Essentially, this code allows you to increase the count by 1 each time the button is clicked.

useEffect

The useEffect hook lets you perform side effects in your components. Side effects can include tasks like making network requests, modifying the DOM, reading/writing to a file system, or interacting with a database. These operations can potentially have unintended consequences on the program’s behavior. useEffect runs after the render is committed to the screen.

import React, { useState, useEffect } from ‘react’;

function Example() {

  const [data, setData] = useState([]);

  useEffect(() => {

    fetch(‘https://jsonplaceholder.typicode.com/users’)

      .then((response) => response.json())

      .then((data) => setData(data));

  }, []);

  return (

    <div>

      <ul>

        {data.map((user) => (

          <li key={user.id}>{user.name}</li>

        ))}

      </ul>

    </div>

  );

}

Here we create a component called “Example” that fetches and displays a list of users from the JSONPlaceholder API.

It declares a state variable data using useState, initialized as an empty array.

The useEffect hook is used to fetch data from the specified URL when the component mounts (indicated by an empty dependency array []).

When the fetch operation completes, the retrieved JSON data is processed and stored in the data state using the setData function.

The component renders an unordered list (<ul>) where each list item (<li>) displays the name of a user from the data array.

Essentially, this code fetches user data from an API, updates the data state with the fetched data, and renders the user names in a list on the web page.

useContext

The useContext is a React hook that enables functional components to access data or state provided by a context without the need to pass props through multiple levels of the component tree. It simplifies the process of sharing information, such as global data or application state, across components.

import { useContext, useState, createContext } from ‘react’;

const MyContext = createContext();

function ParentComponent() {

  const [user, setUser] = useState(‘Jane’);

  return (

    <MyContext.Provider value={user}>

<ChildComponent />

    </MyContext.Provider>

  );

}

function ChildComponent() {

  const user = useContext(MyContext);

  return <h2>{`Hello ${user}}</h2>;

}

Here we intend to use the useContext hook to share the user state between the ParentComponent and ChildComponent using a React context.

  • In ParentComponent, a state variable user is initialized with the value ‘Jane’.
  • The ParentComponent wraps the ChildComponent with a MyContext.Provider, providing the user state as the context value.
  • In ChildComponent, the useContext hook is used to access the user context. It attempts to display a greeting message with the value from the context but contains a syntax error in the template string.

useRef

The useRef hook allows you to create a mutable object that persists across renders. Mutable objects are data structures whose contents or values can be modified after their creation. In programming, this means you can change the data within the object without creating a new instance.

It’s commonly used to access and modify DOM elements directly.

import React, { useRef, useEffect } from ‘react’;

function FocusInput() {

  const inputRef = useRef(null);

  useEffect(() => {

    inputRef.current.focus();

  }, []);

  return <input ref={inputRef} />;

}

Here the component, “FocusInput,” uses the useRef hook to create a reference to an input element.

  • The inputRef is initialized with a null value.
  • Within the useEffect hook, it focuses on the input element by calling inputRef.current.focus() when the component initially mounts.
  • This effectively sets the keyboard cursor to the input element when the component is rendered.
  • The component renders an input element, and the inputRef is attached to it through the ref attribute.

useReducer

The useReducer hook is an alternative to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one.

useReducer is useful for scenarios where you have complex state logic or need to manage multiple state transitions.

One such scenario could be building a shopping cart in an e-commerce application. We use useReducer to handle actions like adding items to the cart, updating quantities, and removing items, all while keeping the cart’s state consistent and predictable. This approach centralizes the logic for state changes, making it easier to maintain and debug..

Here’s a basic example of how useReducer works:

import React, { useReducer } from ‘react’;

// Reducer function

const counterReducer = (state, action) => {

  switch (action.type) {

    case ‘INCREMENT’:

      return { count: state.count + 1 };

    case ‘DECREMENT’:

      return { count: state.count – 1 };

    default:

      return state;

  }

};

function Counter() {

  const [state, dispatch] = useReducer(counterReducer, { count: 0 });

  return (

    <div>

      <p>Count: {state.count}</p>

      <button onClick={() => dispatch({ type: ‘INCREMENT’ })}>Increment</button>

      <button onClick={() => dispatch({ type: ‘DECREMENT’ })}>Decrement</button>

    </div>

  );

}

Here we demonstrate the use of the useReducer hook in a React functional component called “Counter” to manage a counter’s state.

  • A counterReducer function is defined, which takes the current state and an action as input and returns the new state based on the action type.
  • In the “Counter” component, useReducer is used to initialize the state using the counterReducer and an initial state of { count: 0 }.
  • The component renders a paragraph displaying the current count, along with “Increment” and “Decrement” buttons.
  • Clicking the “Increment” button dispatches an action with the type ‘INCREMENT’, which updates the count by increasing it by 1.
  • Clicking the “Decrement” button dispatches an action with the type ‘DECREMENT’, which updates the count by decreasing it by 1.

The dispatch function is used to trigger state changes based on the defined actions, allowing you to manage and update the count state in a more predictable and centralized way.

useMemo

The useMemo hook is used for memoizing expensive calculations or computations. When a function is memoized, its return values are stored based on the input arguments. If the function is called again with the same arguments, the cached result is returned instead of re-computing the result, saving time and computational resources. Memoization is particularly valuable in scenarios where a function’s output depends only on its input and can significantly reduce redundant calculations in recursive or repetitive operations. It helps in optimizing performance by avoiding unnecessary recalculations of values during re-renders.

Here’s an example where useMemo can be beneficial:

import React, { useState, useMemo } from ‘react’;

function ExpensiveCalculation({ data }) {

  const expensiveResult = useMemo(() => {

    // Perform a computationally expensive operation with ‘data’

    return data.reduce((acc, val) => acc + val, 0);

  }, [data]);

  return <div>Result: {expensiveResult}</div>;

}

This React component, “ExpensiveCalculation,” uses the useMemo hook to optimize the calculation of an expensive operation based on the data prop.

  • It receives a data prop, which presumably represents an array of numbers.
  • The useMemo hook caches the result of the expensive operation, which is the sum of all elements in the data array.
  • The calculation only runs when the data prop changes, thanks to the dependency array [data].
  • The component renders the computed result, avoiding unnecessary recalculation of the expensive operation when other component re-renders occur.

This code ensures that the expensive calculation is performed efficiently, reducing unnecessary computations when the data prop remains the same between renders.

useCallback

The useCallback hook is used to memoize functions, similar to useMemo, but for functions instead of values. It’s especially useful when you need to pass functions as props to child components to prevent unnecessary re-renders.

Here’s an example:

import React, { useState, useCallback } from ‘react’;

function ParentComponent() {

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

  const increment = useCallback(() => {

    setCount(count + 1);

  }, [count]);

  return (

    <div>

      <p>Count: {count}</p>

      <ChildComponent increment={increment} />

    </div>

  );

}

function ChildComponent({ increment }) {

  return <button onClick={increment}>Increment</button>;

}

This React code demonstrates the use of the useCallback hook to optimize the performance of a child component and manage a count state in a parent component.

  • In the ParentComponent, a count state variable is initialized with a value of 0. The increment function is created using useCallback, with a dependency array that includes count.
  • The ChildComponent receives the increment function as a prop and renders a button labeled “Increment.”
  • When the “Increment” button is clicked, it invokes the increment function from the parent, which increments the count state by 1.
  • Using useCallback with the [count] dependency array ensures that the increment function is memoized and only recreated when the count state changes. This optimization helps prevent unnecessary re-renders of the ChildComponent.

In summary, the code efficiently manages the increment function to update the count state in the parent component while minimizing unnecessary re-renders of the child component.

Conclusion

React Hooks have significantly improved the way we write React applications. They offer a more concise and functional approach to handling state, effects, and context in function components. By mastering the basic and additional hooks, you can build more maintainable and efficient React applications.