Skip to content

React Hooks Explained: The Ultimate Guide for Developers

React hooks explained: what they are, why they matter, how the key hooks work in function components, and the common mistakes to avoid.
TipUpdated 3 min readDevelopmentReact Js

Introduction

React Hooks revolutionised the way developers build React applications by enabling functional components to handle state and side effects without relying on class components. Since their introduction in React 16.8, Hooks have become a crucial part of modern React development.

In this article, we'll explore React Hooks in depth, covering their purpose, how they work, best practices, and common mistakes to avoid, ensuring you can effectively integrate them into your projects.

What are React Hooks?

React Hooks are special JavaScript functions that allow functional components to use state, lifecycle methods, and other React features without needing a class component. They make React code cleaner, more reusable, and easier to manage.

Why Use React Hooks?

Hooks bring several advantages to React development, including:

  • Simplified Code: No need for complex class components.
  • Better Performance: Optimised re-rendering and memoisation.
  • Reusable Logic: Custom Hooks enable reusable stateful logic.
  • Cleaner Component Structure: Improved readability and maintainability.
  • Easier Testing: Functional components with Hooks are easier to test than class components.

Key React Hooks Explained and Their Usage

1. useState: Managing Local State

The useState Hook allows functional components to store and update state.

import { useState } from 'react';

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

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increase</button>
    </div>
  );
}

Two details worth knowing early:

  • Each useState call holds its own value. Calling it three times in one component gives you three independent pieces of state.
  • If the starting value is expensive to work out, pass a function instead of a value. React runs it only on the first render (lazy initialisation).
const [value, setValue] = useState(() => expensiveComputation());

2. useEffect: Handling Side Effects

The useEffect Hook is used to handle side effects like data fetching, updating the DOM, or subscribing to events.

import { useEffect } from 'react';

function Example() {
  useEffect(() => {
    console.log("Component Mounted");
  }, []); // Runs once when the component mounts

  return <div>Hello, World!</div>;
}
  • Use dependency arrays to control execution.
  • Clean up subscriptions to avoid memory leaks.
  • Separate concerns by using multiple useEffect calls.

The dependency array decides when the effect runs again:

  • Empty array ([]): once, after the first render.
  • Array with values ([userId]): after the first render and whenever one of those values changes.
  • No array at all: after every render. Rarely what you want.

If the effect subscribes to something, return a cleanup function. React calls it before the effect runs again and when the component is removed, which prevents memory leaks:

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

function WindowSize() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);

    window.addEventListener('resize', handleResize);

    // Cleanup function to remove the event listener
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []); // Empty array ensures this effect is added only once

  return <p>Window width: {width}px</p>;
}

export default WindowSize;

3. useContext: Avoiding Props Drilling

useContext simplifies state sharing between components without passing props manually.

import { useContext } from 'react';
import { ThemeContext } from './ThemeContext';

function ThemedComponent() {
  const theme = useContext(ThemeContext);
  return <div style={{ backgroundColor: theme.background }}>Themed Component</div>;
}

4. useRef: Accessing DOM Elements & Persisting Values

The useRef Hook allows direct interaction with DOM elements without causing re-renders.

import { useRef } from 'react';

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

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

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

5. useReducer: Managing Complex State Logic

The useReducer Hook is an alternative to useState for handling complex state logic.

import { useReducer } from 'react';

const initialState = { count: 0 };

function reducer(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(reducer, initialState);

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  );
}

6. useMemo: Optimising Performance

The useMemo Hook memoises expensive calculations to improve performance.

import { useMemo } from 'react';

function ExpensiveCalculation({ num }) {
  const squaredValue = useMemo(() => {
    console.log("Calculating...");
    return num * num;
  }, [num]);

  return <p>Squared Value: {squaredValue}</p>;
}

7. useCallback: Memoising Functions

The useCallback Hook prevents unnecessary function re-creations.

import { useCallback } from 'react';

function ParentComponent() {
  const handleClick = useCallback(() => {
    console.log("Button clicked!");
  }, []);

  return <button onClick={handleClick}>Click Me</button>;
}

The less common hooks, and when they earn their place

The seven hooks above cover almost every component. React ships a few more for specific problems: layout measurement, ref control, responsiveness and library code. You will not need them often, but it helps to know they exist.

HookWhat it is for
useLayoutEffectRuns synchronously after the DOM updates and before the browser paints. Use it to measure layout and avoid a visible flicker
useImperativeHandleControls which methods a component exposes to a parent through a ref, for example focus() or scrollToTop()
useDeferredValueLets an expensive part of the screen lag behind fast input, such as a results list while the user types
useIdGenerates a stable unique ID, for example to link a label to its input, that matches between server and client rendering
useDebugValueShows a custom label for your own hook in React DevTools
useInsertionEffectInjects styles before layout effects run. Meant for CSS-in-JS library authors, not application code

Reaching for these, or wrapping everything in useMemo and useCallback, is not a sign of seniority. Each has a cost of its own. Use them when you have a specific problem they solve, ideally after you have measured it.

Common Mistakes to Avoid with React Hooks

  • Forgetting Dependency Arrays in useEffect: Leads to infinite loops or unnecessary re-executions.
  • Mutating State Directly: Always use the state setter function.
  • Not Using Memoisation for Performance Optimisation: Leads to redundant computations.
  • Using Hooks in Loops or Conditions: Always call Hooks at the top level of the component.

Conclusion

React Hooks simplify the development process by eliminating class components' complexity while making functional components more powerful. By mastering Hooks, developers can build more maintainable and scalable applications efficiently.

FAQs

1. What are the benefits of using React Hooks?

  • Simplifies code by eliminating class components
  • Improves readability and reusability
  • Enhances performance with useMemo and useCallback
  • Makes component logic easier to share via custom Hooks

2. Can I use Hooks inside class components?

No, Hooks are exclusive to functional components.

3. What is the difference between useEffect and useLayoutEffect?

useEffect runs asynchronously after render, while useLayoutEffect runs synchronously before the browser paints the screen.

4. How can I share state logic between components?

You can use the Context API with useContext or create custom Hooks.

Share this
Build work

Need this built properly?

I build the web and mobile products these notes come out of, in React, React Native and Node. A first conversation costs nothing and often ends with a smaller scope than you expected.

Not ready to book a call? Get a free website health check