Most Commonly Used React Hooks Comparison

Compare the most commonly used React Hooks and learn when to use which Hook for better state management and performance.

Loading...
Most Commonly Used React Hooks Comparison

If React, with so many available hooks, choosing the right one can be confusing - especially if you're just getting started or trying to optimize your code. Each hook serves a specific purpose, and understanding when and why to use them is key to writing clean, efficient, and maintainable React apps.

To make things simpler, here's a comparison of some of the most commonly used React hooks. This quick comparison will help you understand what each hook does, when to use it, and how it fits into your component logic.

useState vs. useReducer

Use useState when:

  • The state is simple, and you need to track one or two variables.
  • Example: Counter, form input handling.
import { useState } from "react";
 
function Counter() {
  const [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount(count + 1)}>Count: {count}</button>
  );
}

Here, useState is used for creating a simple counter. On button click, the setCount function updates the state.

Use useReducer when:

  • The state is complex, and you need to handle multiple state updates together.
  • Example: Todo app, form validation.
import { useReducer, useState } from "react";
 
function todoReducer(state, action) {
  switch (action.type) {
    case "add":
      return [...state, { text: action.payload, completed: false }];
    case "toggle":
      return state.map((todo, index) =>
        index === action.index ? { ...todo, completed: !todo.completed } : todo
      );
    case "remove":
      return state.filter((_, index) => index !== action.index);
    default:
      return state;
  }
}
 
function TodoApp() {
  const [todos, dispatch] = useReducer(todoReducer, []);
  const [input, setInput] = useState("");
 
  return (
    <div>
      <h2>Todo App using useReducer</h2>
      <input value={input} onChange={(e) => setInput(e.target.value)} />
      <button onClick={() => dispatch({ type: "add", payload: input })}>Add Todo</button>
      <ul>
        {todos.map((todo, index) => (
          <li key={index} style={{ textDecoration: todo.completed ? "line-through" : "none" }}>
            {todo.text}
            <button onClick={() => dispatch({ type: "toggle", index })}>Toggle</button>
            <button onClick={() => dispatch({ type: "remove", index })}>Remove</button>
          </li>
        ))}
      </ul>
    </div>
  );
}
 
export default TodoApp;

Here, useReducer is used for complex state management. The dispatch function triggers the actions that modify the state.


useEffect vs. useLayoutEffect

Use useEffect when:

  • You need to perform side effects after rendering the component.
  • Example: API calls, event listeners, timers.
import { useEffect, useState } from "react";
 
function FetchData() {
  const [data, setData] = useState(null);
 
  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/todos/1")
      .then((response) => response.json())
      .then((json) => setData(json));
  }, []);
 
  return <pre>{JSON.stringify(data, null, 2)}</pre>;
}

Here, useEffect is used to fetch data from an API. This API sends the request after the component renders.

Use useLayoutEffect when:

  • You need to modify something right after the DOM update.
  • Example: Animation adjustments, measuring DOM elements.
import { useLayoutEffect, useRef } from "react";
 
function Box() {
  const boxRef = useRef(null);
 
  useLayoutEffect(() => {
    console.log("Box width:", boxRef.current.offsetWidth);
  });
 
  return <div ref={boxRef} style={{ width: "100px", height: "100px", background: "red" }} />;
}

Here, useLayoutEffect is used to measure the width of the element right after the DOM update.


useRef vs. useMemo vs. useCallback

Use useRef when:

  • You need to reference a DOM element directly.
  • You need to track the state update without re-rendering.
import { useRef } from "react";
 
function InputFocus() {
  const inputRef = useRef(null);
 
  return (
    <>
      <input ref={inputRef} type="text" />
      <button onClick={() => inputRef.current.focus()}>Focus</button>
    </>
  );
}

Here, useRef is used to directly refer to the input field so that on button click, the focus can be set.

Use useMemo when:

  • You need to optimize expensive calculations.
  • You need to avoid unnecessary re-renders.
import { useState, useMemo } from "react";
 
function ExpensiveComponent({ numbers }) {
  function sum(numbers) {
    console.log("Calculating sum...");
    return numbers.reduce((acc, num) => acc + num, 0);
  }
 
  const [count, setCount] = useState(0);
 
  const total = useMemo(() => sum(numbers), [numbers]); // Recalculates only when `numbers` change
 
  return (
    <div>
      <p>Sum: {total}</p>
      <button onClick={() => setCount(count + 1)}>Re-render: {count}</button>
    </div>
  );
}
 
export default function App() {
  return <ExpensiveComponent numbers={[1, 2, 3, 4, 5]} />;
}

Here, useMemo is used to cache expensive calculations so that this doesn’t recalculate on each render.

Use useCallback when:

  • You need to memoize a function so that it doesn’t re-create unnecessarily.
  • You need to prevent the child component from unnecessary re-renders.
import { useState, useCallback } from "react";
 
function ChildComponent({ handleClick }) {
  console.log("Child re-rendered!"); // This will only re-render when function changes
 
  return <button onClick={handleClick}>Click Me</button>;
}
 
export default function App() {
  const [count, setCount] = useState(0);
 
  const handleClick = useCallback(() => {
    console.log("Button clicked!");
  }, []); // Function reference will not change
 
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <ChildComponent handleClick={handleClick} />
    </div>
  );
}

Here, useCallback is used to memoize the function so that the Button component doesn’t re-render unnecessarily.


useContext

Use useContext when:

  • You need to share the global state without props drilling.
  • Example: Theme toggle, user authentication.
import { createContext, useContext, useState } from "react";
 
const ThemeContext = createContext();
 
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState("light");
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}
 
function ThemedButton() {
  const { theme, setTheme } = useContext(ThemeContext);
  return (
    <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>Toggle Theme</button>
  );
}

Here, useContext is used to share a global state (theme) so that multiple components can access the state without prop drilling.


Comparison Table

Hook When to use?
useState For simple state
useReducer For complex state logic
useEffect For side effects and async tasks
useLayoutEffect For UI modifications after DOM updates
useRef For DOM reference and mutable values
useMemo For optimizing expensive calculations
useCallback For optimizing function re-creation
useContext For global state management

Using the right React hook is important for code maintainability and performance optimization.