Understanding ReactJS Hooks: A Comprehensive Guide

Published on  

19 min read

Understanding ReactJS Hooks: A Comprehensive Guide

React Hooks were introduced in React 16.8 and they are functions that allow you to use state and other React features without writing a class component.

ReactJS has gained immense popularity over the years due to its ability to build complex and interactive web applications. One of the key features of ReactJS is the introduction of hooks, which has revolutionized the way developers write code. In this article, we will discuss ReactJS hooks in detail and understand how they work.

What are ReactJS Hooks?

ReactJS Hooks are functions that allow you to use React state and lifecycle features from function components. Before the introduction of hooks, developers had to use class components to manage state and lifecycle methods. With hooks, developers can use state and lifecycle methods in functional components, making it easier to write clean and reusable code.

There are several built-in hooks in ReactJS, including useState, useEffect, useContext, and useReducer. These hooks provide a way to manage state, perform side effects, and interact with the React context API.

Types of React Hooks

There are several types of Hooks available in React:

  1. useState: This hook is used to add state to functional components.
  2. useEffect: This hook is used to add side effects to your components. It allows you to perform actions when certain conditions are met, such as when the component mounts or when the component updates.
  3. useContext: This hook is used to access context within your components. It allows you to pass data from a parent component to a child component without having to pass props down through each level of the component tree.
  4. useReducer: This hook is used to manage complex state logic within your components. It is similar to useState, but is more suitable for managing state that has multiple sub-values or when the next state depends on the previous state.
  5. useCallback: This hook is used to memoize functions so that they are only re-created when their dependencies change. This can help with performance when passing functions as props to child components.
  6. useMemo: This hook is used to memoize values so that they are only re-calculated when their dependencies change. This can help with performance when performing expensive calculations within your components.
  7. useRef: This hook is used to create a mutable reference that persists between renders. It can be used to access the DOM, manage timers/intervals, or store any mutable value that needs to persist between renders.
  8. useLayoutEffect: This hook is similar to useEffect, but it is executed synchronously immediately after all DOM mutations have been applied. This makes it useful for interacting with the DOM or measuring element sizes/positions.
  9. useImperativeHandle: This hook is used to expose certain methods of a child component to its parent component. It is commonly used when working with third-party libraries that require direct access to a component's methods.
  10. useDebugValue: This hook is used to display custom labels for custom hooks in React DevTools. It can be used to add more context to the hooks used within your application.
  11. Custom Hooks: Custom hooks allow developers to create reusable logic that can be shared across multiple components.

1. useState Hook

The useState hook is used to manage state in functional components. It takes an initial value as its argument and returns an array with two elements: the current state value and a function to update the state.

import React, { useState } from 'react';

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

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

In the example above, we are using the useState hook to manage the count state. We initialize the state to 0 and update it using the setCount function.

2. useEffect Hook

The useEffect hook is used to perform side effects in functional components. It takes a function as its argument and runs it after every render.

import React, { useState, useEffect } from 'react';

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

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

In the example above, we are using the useEffect hook to update the document title after every render. The function passed to useEffect is called after every render, which means that the title will be updated every time the count state changes.

3. useContext Hook

The useContext hook is used to consume values from the React context API. It takes a context object as its argument and returns the current context value.

import React, { useContext } from 'react';
import { ThemeContext } from './theme-context';

function ThemeButton() {
  const theme = useContext(ThemeContext);

  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}

In the example above, we are using the useContext hook to consume the ThemeContext value. We can use this value to style the button based on the current theme.

4. useReducer Hook

The useReducer hook is used to manage complex state in functional components. It takes a reducer function and an initial state as its arguments and returns an array with two elements: the current state value and a dispatch function to update the state.

import React, { useReducer } from 'react';

// Define initial state
const initialState = { count: 0 };

// Define reducer function
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

function Counter() {
  // Call useReducer hook
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <h2>Count: {state.count}</h2>
      <button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
    </div>
  );
}

export default Counter;

In this example, we define an initial state object { count: 0 } and a reducer function that takes the current state and an action object as arguments and returns the updated state based on the action type.

We then call the useReducer hook and pass in the reducer function and initial state. This returns an array with the current state and a dispatch function that we can use to update the state by passing in an action object with a specific type.

In the Counter component, we display the current count value from the state object and render two buttons that call the dispatch function with the appropriate action object when clicked. This updates the state and triggers a re-render of the component with the updated count value.

5. useCallback Hook

This hook is used to memoize a function in a functional component. It is similar to the shouldComponentUpdate method in class components. Here's an example:

import React, { useCallback, useState } from 'react';

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

  // This function will be memoized with useCallback
  const handleClick = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Click me!</button>
    </div>
  );
}

In this example, we're using useCallback to memoize the handleClick function so that it's not recreated on every render. This can be useful for performance optimization, especially when passing functions down to child components that may trigger unnecessary renders.

The useCallback hook takes two arguments: a callback function and a dependencies array. The callback function is the function that will be memoized, and the dependencies array is an array of values that the function depends on. If any of these values change, the function will be re-created.

In our example, the handleClick function depends on the count state variable, so we include it in the dependencies array. This ensures that the function is only re-created when the count variable changes.

By using useCallback in this way, we can improve the performance of our app by reducing unnecessary renders and optimizing our function creation.

6. useMemo Hook

useMemo is a React hook that allows you to memoize the result of a function so that it is only re-computed when the inputs change. It is useful when you have a costly computation that doesn't need to be re-computed on every render.

Here's an example of how to use useMemo:

import React, { useState, useMemo } from 'react';

function Example() {
  const [countA, setCountA] = useState(0);
  const [countB, setCountB] = useState(0);

  const total = useMemo(() => {
    console.log('Calculating total...');
    return countA + countB;
  }, [countA, countB]);

  return (
    <div>
      <h1>useMemo Example</h1>
      <p>Count A: {countA}</p>
      <p>Count B: {countB}</p>
      <button onClick={() => setCountA(countA + 1)}>Increment A</button>
      <button onClick={() => setCountB(countB + 1)}>Increment B</button>
      <p>Total: {total}</p>
    </div>
  );
}

export default Example;

In this example, we are using the useMemo hook to memoize the calculation of the total variable. The useMemo hook takes two arguments: a function that calculates the memoized value, and an array of dependencies that trigger a re-calculation of the memoized value when they change.

In this case, we only want to re-calculate the total value when either countA or countB change, so we include them in the dependencies array. The first time the component renders, the total value is calculated and stored, and subsequent re-renders will use the memoized value instead of re-calculating it every time.

This can be especially useful when dealing with expensive calculations or complex data structures that don't need to be re-calculated on every re-render. By memoizing the calculation using useMemo, we can optimize our component's performance and avoid unnecessary re-renders.

7. useRef Hook

useRef is a hook in React that returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). It is used to access DOM nodes or to store any mutable value that does not trigger a re-render of the component. It can also be used to hold on to any value that persists between renders.

Here's an example of how to use the useRef hook:

import React, { useRef } from 'react';

function MyComponent() {
  const inputRef = useRef(null);

  const handleClick = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      <input type="text" ref={inputRef} />
      <button onClick={handleClick}>Focus Input</button>
    </div>
  );
}

In this example, we are using the useRef hook to create a reference to the input element. We then pass this ref to the input element using the ref prop. In the handleClick function, we access the current property of the ref object to focus the input element.

Note that the useRef hook does not cause a component to re-render when its value changes, which is useful for storing values that you do not want to trigger a re-render. However, the useRef hook should not be used to manage state, as it does not cause a re-render when its value changes. For state management, use the useState or useReducer hooks.

8. useLayoutEffect Hook

The useLayoutEffect hook in React is very similar to the useEffect hook, but it is executed synchronously immediately after all DOM mutations. It is often used to measure the size or position of a DOM element and perform some action accordingly.

The useLayoutEffect hook takes two arguments - a callback function and an optional array of dependencies.

Here is an example of how to use the useLayoutEffect hook to measure the height of a DOM element:

import React, { useLayoutEffect, useState, useRef } from 'react';

function Example() {
  const [height, setHeight] = useState(0);
  const elementRef = useRef(null);

  useLayoutEffect(() => {
    setHeight(elementRef.current.clientHeight);
  }, []);

  return (
    <div ref={elementRef}>
      The height of this div is {height}px.
    </div>
  );
}

In this example, we first define a state variable height and a ref object elementRef. We then use the useLayoutEffect hook to measure the height of the div element referenced by elementRef and set the height state variable to the measured height.

Note that we pass an empty array as the second argument to useLayoutEffect, which ensures that the effect is only executed once, immediately after the DOM mutation caused by rendering the component. This is necessary to ensure that we get an accurate measurement of the height of the element.

In the return statement, we simply render the div element and display the height variable.

9. useImperativeHandle Hook

The useImperativeHandle hook is a way to expose certain functions or values of a child component to its parent component. This hook can be useful in cases where you want to provide a way for the parent component to interact with the child component in a specific way.

Here's an example of how to use useImperativeHandle:

import React, { forwardRef, useImperativeHandle } from 'react';

const ChildComponent = forwardRef((props, ref) => {
  const inputRef = useRef();

  useImperativeHandle(ref, () => ({
    focusInput: () => {
      inputRef.current.focus();
    }
  }));

  return (
    <input type="text" ref={inputRef} />
  );
});

export default ChildComponent;

In this example, the ChildComponent is a simple text input field. The useImperativeHandle hook is used to expose a function called focusInput that can be called from the parent component to focus the input field.

The forwardRef function is used to forward the ref prop to the ChildComponent. This allows the parent component to access the ref and call the focusInput function.

Here's an example of how to use the ChildComponent in a parent component:

import React, { useRef } from 'react';
import ChildComponent from './ChildComponent';

const ParentComponent = () => {
  const childRef = useRef();

  const handleButtonClick = () => {
    childRef.current.focusInput();
  };

  return (
    <div>
      <ChildComponent ref={childRef} />
      <button onClick={handleButtonClick}>Focus input</button>
    </div>
  );
};

export default ParentComponent;

In this example, the ParentComponent renders the ChildComponent and provides a way to call the focusInput function. When the button is clicked, the handleButtonClick function is called, which calls the focusInput function on the ChildComponent using the childRef.

Note that the useImperativeHandle hook should be used sparingly, as it can make your code harder to reason about and maintain. It's generally recommended to use it only when necessary and to keep the exposed functions or values as simple as possible.

10. useDebugValue Hook

The useDebugValue hook is a utility hook that helps to debug custom hooks. It allows you to display custom hook values in React Developer Tools. This hook takes two arguments, a value and a formatter function, and returns the value passed to it.

Here's an example of how to use the useDebugValue hook:

import { useDebugValue, useState } from 'react';

function useCustomHook(initialValue) {
  const [value, setValue] = useState(initialValue);
  useDebugValue(value, value => `Value: ${value}`);

  const updateValue = (newValue) => {
    setValue(newValue);
  };

  return { value, updateValue };
}

In the example above, useCustomHook is a custom hook that takes an initial value as an argument and returns an object with a value property and an updateValue function. The useDebugValue hook is used to display the value of value in React Developer Tools. The formatter function formats the value to display it in a specific way.

Using the useDebugValue hook can help you debug your custom hooks and make it easier to see what values they are returning. It is particularly useful when working with complex hooks that have multiple values or when you need to inspect the values of a custom hook in React Developer Tools.

What is a Custom Hook and How to Create it?

In ReactJS, a custom hook is a function that allows you to reuse stateful logic across your components. By creating a custom hook, you can abstract complex logic into a reusable function that can be easily imported and used in different components.

Custom hooks are created using the same basic syntax as a regular hook: they start with the word "use" and can use other hooks within them. For example, here's a custom hook that uses the useState hook to manage the state of a boolean value:

import { useState } from 'react';

function useToggle(initialState) {
  const [value, setValue] = useState(initialState);

  function toggleValue() {
    setValue(!value);
  }

  return [value, toggleValue];
}

This custom hook, called useToggle, takes an initial state value and returns an array with two elements: the current value of the boolean and a function to toggle it. To use this custom hook in a component, you simply import it and call it like any other hook:

import { useToggle } from './useToggle';

function MyComponent() {
  const [isOn, toggleIsOn] = useToggle(false);

  return (
    <div>
      <p>The current value is: {isOn ? 'on' : 'off'}</p>
      <button onClick={toggleIsOn}>Toggle</button>
    </div>
  );
}

This example demonstrates the power of custom hooks - by creating a reusable function that abstracts away the complexity of state management, we can easily use it in multiple components and keep our code clean and modular.

Conclusion

React hooks provide a simpler and more efficient way to write React code. They allow you to use React features in functional components, which reduces the amount of boilerplate code needed. Additionally, they allow you to reuse stateful logic across components, which can be a huge time saver. If you're not using hooks yet, give them a try in your next project.

If you want to learn more about React and other related topics, check out our other posts on ReactJS and getting started with ReactJS.

TAGS:

About the Author

Rexposed Staff

Was this helpful?