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.
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.