Popular Searches
Popular Course Categories
Popular Courses

Choosing the Right Hook

Choosing the Right Hook

Advanced React Hooks

Choosing the Right Hook in React JS – Complete Notes

React Hooks are special functions that allow function components to use React features such as state management, context, references, effects, and performance optimization. Choosing the right Hook helps developers write clean, reusable, maintainable, and efficient React applications.

The right Hook depends on what you want to achieve. For example, use useState to manage simple state, useReducer for complex state transitions, useEffect to synchronize with external systems, and useRef to store values or access DOM elements without triggering a re-render.

Official Documentation: React Built-in Hooks Documentation


1. What Does Choosing the Right Hook Mean?

Choosing the right Hook means selecting the React Hook that best matches a component's requirements instead of using the same Hook for every situation.

React provides different Hooks for different purposes. Understanding their responsibilities helps prevent unnecessary complexity, incorrect state handling, avoidable effects, and performance problems.

Why Is It Important?

  • Helps manage component state correctly.
  • Makes code easier to read, test, and maintain.
  • Prevents unnecessary re-renders and calculations when optimization is appropriate.
  • Helps synchronize React components with external systems.
  • Encourages reusable logic through custom Hooks.
  • Reduces bugs caused by duplicated or incorrectly managed state.

2. Overview of Common React Hooks

Hook Primary Purpose When to Use
useState Manage local state. Forms, counters, toggles, and visibility controls.
useEffect Synchronize with external systems. Event subscriptions, timers, network connections, and third-party integrations.
useContext Read context values. Sharing themes, authentication information, or locale settings across components.
useReducer Manage state through a reducer function. Complex state transitions and related state updates.
useRef Store a mutable value or DOM reference. Focusing inputs, tracking timer IDs, or storing values that do not affect rendering.
useMemo Cache a calculation result. Expensive calculations that can benefit from reuse.
useCallback Cache a function definition. When function identity matters for optimized children or other dependencies.
useLayoutEffect Run an effect before browser repaint. Measuring layout or making visual corrections before the screen is painted.
useImperativeHandle Customize a ref's exposed handle. Exposing specific child methods such as focus().
useId Generate unique IDs for accessibility relationships. Connecting form labels and controls.
useTransition Mark updates as non-blocking transitions. Keeping urgent interactions responsive while rendering non-urgent UI updates.
useDeferredValue Defer updating a non-critical value. Keeping typing responsive when a dependent UI update is expensive.
Custom Hooks Reuse stateful React logic. Sharing behavior such as form logic, window events, or application-specific data handling.

3. When Should You Use useState?

Use useState when a component needs to remember a value between renders and changes to that value should update the UI.

Common Examples

  • Counter values.
  • Input field values.
  • Show or hide a menu.
  • Open or close a modal.
  • Track selected tabs or filters.

Example: Counter

import { useState } from "react";

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

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

export default Counter;

Explanation

  • useState(0) initializes the counter to zero.
  • count contains the current state value.
  • setCount requests a state update.
  • React renders the component again when the state changes.

Choose useState when: The state is relatively simple and can be updated with straightforward setter functions.

Avoid using it unnecessarily when: A value can be calculated directly from existing props or state, rather than stored separately.


4. When Should You Use useReducer?

Use useReducer when state updates involve multiple related values, several action types, or logic that is easier to organize in one reducer function.

Common Examples

  • Shopping carts.
  • Multi-step forms.
  • Complex form state.
  • Task management applications.
  • Components with several related state transitions.

Example: Task Counter

import { useReducer } from "react";

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
    case "reset":
      return { count: 0 };
    default:
      throw new Error("Unknown action: " + action.type);
  }
}

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

  return (
    <div>
      <h2>Count: {state.count}</h2>
      <button onClick={() => dispatch({ type: "increment" })}>
        Increase
      </button>
      <button onClick={() => dispatch({ type: "decrement" })}>
        Decrease
      </button>
      <button onClick={() => dispatch({ type: "reset" })}>
        Reset
      </button>
    </div>
  );
}

export default TaskCounter;

Explanation

  • useReducer receives a reducer function and an initial state.
  • dispatch() sends an action describing what should happen.
  • The reducer determines the next state based on the current state and action.
  • The component renders with the updated state.

Choose useReducer when: State transitions are complex or related updates are easier to manage centrally. For simple state, useState is often sufficient.


5. When Should You Use useEffect?

Use useEffect to synchronize a component with an external system. External systems include browser APIs, timers, subscriptions, network connections, and third-party libraries.

Common Examples

  • Setting up and cleaning up event listeners.
  • Connecting to a chat server.
  • Starting or clearing timers.
  • Synchronizing with a third-party widget.
  • Fetching data when an appropriate external synchronization pattern is needed.

Example: Updating the Browser Tab Title

import { useState, useEffect } from "react";

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

  useEffect(() => {
    document.title = `Count: ${count}`;
  }, [count]);

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

export default TitleUpdater;

Explanation

  • The component stores the count using useState.
  • The effect synchronizes the browser tab title with the current count.
  • The [count] dependency means React reruns the effect when the count changes.

Important: Do not use useEffect for every calculation or state update. If a value can be calculated during rendering from existing props and state, calculate it directly. Effects are primarily for synchronization with systems outside React.


6. When Should You Use useContext?

Use useContext when a component needs to read a value provided by a context higher in the component tree, without passing that value through every intermediate component.

Common Examples

  • Application themes.
  • Authenticated user information.
  • Language and locale preferences.
  • Shared application settings.

Example: Theme Context

import { createContext, useContext } from "react";

const ThemeContext = createContext("light");

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

  return <p>Current theme: {theme}</p>;
}

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <ThemeLabel />
    </ThemeContext.Provider>
  );
}

export default App;

Explanation

  • createContext() creates a context.
  • The provider supplies the value to components within its tree.
  • useContext() reads the nearest matching provider's value.
  • When the context value changes, components reading that context update as needed.

Choose useContext when: Multiple components need shared information that would otherwise require excessive prop passing. Context does not automatically replace all state-management approaches.


7. When Should You Use useRef?

Use useRef when you need to store a value between renders without triggering a re-render when that value changes, or when you need a reference to a DOM element.

Common Examples

  • Focusing an input field.
  • Storing a timer ID.
  • Keeping track of a previous value.
  • Accessing a DOM element.
  • Interacting with browser APIs or imperative libraries.

Example: Focus an Input

import { useRef } from "react";

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

  function handleFocus() {
    inputRef.current?.focus();
  }

  return (
    <div>
      <input ref={inputRef} placeholder="Enter your name" />
      <button onClick={handleFocus}>
        Focus Input
      </button>
    </div>
  );
}

export default FocusInput;

Choose useRef when: You need a mutable reference or DOM access, and changing the stored value should not itself cause a render.

Do not use useRef as a replacement for useState when a changed value must appear in the rendered UI.


8. When Should You Use useMemo?

Use useMemo to cache the result of a calculation between renders when the calculation is expensive and reusing its result is beneficial.

Common Examples

  • Filtering a large list.
  • Sorting a large data set.
  • Calculating complex derived values.
  • Maintaining a stable object value for a performance-sensitive dependency.

Example: Filtering Products

import { useMemo, useState } from "react";

const products = [
  { id: 1, name: "Laptop", price: 50000 },
  { id: 2, name: "Mouse", price: 500 },
  { id: 3, name: "Keyboard", price: 1500 }
];

function ProductList() {
  const [search, setSearch] = useState("");

  const filteredProducts = useMemo(() => {
    return products.filter(product =>
      product.name.toLowerCase().includes(search.toLowerCase())
    );
  }, [search]);

  return (
    <div>
      <input
        value={search}
        onChange={e => setSearch(e.target.value)}
        placeholder="Search products"
      />
      <ul>
        {filteredProducts.map(product => (
          <li key={product.id}>
            {product.name} - ₹{product.price}
          </li>
        ))}
      </ul>
    </div>
  );
}

export default ProductList;

Choose useMemo when: Profiling or the nature of the calculation indicates that caching is useful. Do not add it to every calculation by default; it adds complexity and is not required for correctness.


9. When Should You Use useCallback?

Use useCallback to cache a function definition between renders when a stable function identity is useful, such as when passing a callback to a memoized child component.

Example

import { useState, useCallback, memo } from "react";

const ActionButton = memo(function ActionButton({ onAction }) {
  console.log("ActionButton rendered");

  return <button onClick={onAction}>Perform Action</button>;
});

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

  const handleAction = useCallback(() => {
    console.log("Action performed");
  }, []);

  return (
    <div>
      <h2>Count: {count}</h2>
      <button onClick={() => setCount(c => c + 1)}>
        Increase Count
      </button>
      <ActionButton onAction={handleAction} />
    </div>
  );
}

export default App;

Explanation

  • useCallback caches the function definition while its dependencies remain unchanged.
  • memo can skip rendering the child when its props remain unchanged.
  • A stable callback can help avoid unnecessary child renders in suitable cases.

Choose useCallback when: Function identity affects memoization or a dependency. It is not necessary for every event handler.


10. When Should You Use useLayoutEffect?

Use useLayoutEffect when you must measure or adjust layout before the browser repaints the screen. It runs after React has committed DOM changes but before the browser repaint.

Common Examples

  • Measuring an element's dimensions.
  • Positioning a tooltip before it becomes visible.
  • Correcting layout to prevent a visible jump.

Example: Measure an Element

import { useLayoutEffect, useRef, useState } from "react";

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

  useLayoutEffect(() => {
    if (boxRef.current) {
      setHeight(boxRef.current.getBoundingClientRect().height);
    }
  }, []);

  return (
    <div>
      <div ref={boxRef} style={{ padding: "20px", border: "1px solid" }}>
        Measure this box
      </div>
      <p>Height: {height}px</p>
    </div>
  );
}

export default BoxMeasurement;

Choose useLayoutEffect only when necessary. It can block browser painting. Prefer useEffect for most synchronization tasks that do not need to happen before repaint.


11. When Should You Use useImperativeHandle?

Use useImperativeHandle when a child component needs to expose a small, controlled set of imperative methods through a ref.

Example Use Cases

  • Exposing a focus() method from a custom input.
  • Exposing a clear() method from an input component.
  • Allowing a parent to request a child component to scroll to a section.

Example

import {
  useRef,
  useImperativeHandle,
  forwardRef
} from "react";

const CustomInput = forwardRef(function CustomInput(props, ref) {
  const inputRef = useRef(null);

  useImperativeHandle(ref, () => ({
    focus() {
      inputRef.current?.focus();
    }
  }), []);

  return <input ref={inputRef} placeholder="Enter text" />;
});

export default function App() {
  const customInputRef = useRef(null);

  return (
    <div>
      <CustomInput ref={customInputRef} />
      <button onClick={() => customInputRef.current?.focus()}>
        Focus Input
      </button>
    </div>
  );
}

This example uses forwardRef, which is the conventional approach for React 18 and earlier. In React 19, a function component can receive ref as a prop.

Choose useImperativeHandle sparingly. Prefer declarative props for normal component behavior.


12. When Should You Use a Custom Hook?

A custom Hook is a JavaScript function whose name starts with use and that can call other Hooks. It allows you to reuse stateful logic across multiple components.

Common Examples

  • Reusable form logic.
  • Window size tracking.
  • Online/offline status tracking.
  • Reusable event-listener logic.
  • Application-specific data synchronization.

Example: useCounter

import { useState } from "react";

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

  function increment() {
    setCount(value => value + 1);
  }

  function decrement() {
    setCount(value => value - 1);
  }

  function reset() {
    setCount(initialValue);
  }

  return { count, increment, decrement, reset };
}

function Counter() {
  const { count, increment, decrement, reset } = useCounter(0);

  return (
    <div>
      <h2>Count: {count}</h2>
      <button onClick={increment}>Increase</button>
      <button onClick={decrement}>Decrease</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

export default Counter;

Choose a custom Hook when: Multiple components need the same stateful behavior and extracting that behavior improves reuse and clarity. Calling a custom Hook separately in two components does not automatically make them share one state instance.


13. A Simple Decision Guide for Choosing Hooks

Question Recommended Choice
Does the UI need to update when a local value changes? useState
Are there many related state transitions or complex update rules? useReducer
Do you need to read a value from a context provider? useContext
Do you need to access a DOM element or keep a mutable value that should not trigger rendering? useRef
Do you need to synchronize with an external system? useEffect
Must you measure layout or make a visual correction before repaint? useLayoutEffect
Is an expensive calculation worth caching? useMemo
Does a callback's stable identity matter for optimization? useCallback
Does a child need to expose selected imperative methods through a ref? useImperativeHandle
Do multiple components need the same reusable stateful logic? Custom Hook
Do you need a unique ID for accessibility relationships? useId
Should a non-urgent UI update be interruptible? useTransition
Should a non-critical value update be deferred? useDeferredValue

14. Common Mistakes When Choosing Hooks

Mistake 1: Using useEffect for Every Task

Not every calculation or state update needs an effect. Calculate derived values during rendering whenever possible, and use effects for synchronization with external systems.

Mistake 2: Using useRef for Visible UI State

Changing ref.current does not trigger a re-render. Use useState when a changed value must update the UI.

Mistake 3: Optimizing Everything

Using useMemo and useCallback everywhere can make code harder to understand. Use them when caching provides a meaningful benefit.

Mistake 4: Using useState for Every Complex State Model

Multiple independent state variables can work well, but complex related transitions may be easier to manage with useReducer.

Mistake 5: Using Context for Every Value

Context is useful for values needed by distant components, but ordinary props are often simpler when data is only needed by nearby components.

Mistake 6: Breaking the Rules of Hooks

Call Hooks at the top level of function components or custom Hooks. Do not call them inside loops, conditions, event handlers, or nested functions.

Reference: Official React Rules of Hooks


15. Best Practices for Choosing the Right Hook

  • Understand the requirement before selecting a Hook.
  • Prefer the simplest solution that correctly represents the component's behavior.
  • Use state for values that affect rendering.
  • Use refs for DOM access and mutable values that do not need to trigger rendering.
  • Use effects to synchronize with external systems, not to duplicate calculations unnecessarily.
  • Use reducers when related state transitions become difficult to maintain.
  • Use context for values shared across component trees.
  • Use memoization when there is a real performance reason.
  • Create custom Hooks to reuse stateful logic.
  • Follow the Rules of Hooks and declare dependencies correctly.

16. Interview Questions and Answers

Q1. What does choosing the right Hook mean?

Answer: It means selecting a Hook based on the requirement, such as state management, external synchronization, DOM access, context, or performance optimization.

Q2. What is the difference between useState and useReducer?

Answer: useState is convenient for straightforward state updates, while useReducer organizes more complex state transitions through a reducer function and dispatched actions.

Q3. When should useEffect be used?

Answer: Use it to synchronize a component with external systems, such as browser APIs, subscriptions, timers, or connections.

Q4. What is the main purpose of useRef?

Answer: It stores a mutable value between renders or references a DOM element without triggering a re-render when its current value changes.

Q5. What is the difference between useMemo and useCallback?

Answer: useMemo caches a calculation result, whereas useCallback caches a function definition.

Q6. When should useContext be used?

Answer: Use it when a component needs to read a value provided by a context higher in the component tree.

Q7. Why should useLayoutEffect be used carefully?

Answer: It runs before the browser repaints, so it can block painting. Use it when pre-paint layout measurement or visual correction is necessary.

Q8. What is a custom Hook?

Answer: A custom Hook is a reusable function that starts with use and combines React Hooks to share stateful logic between components.

Q9. Should every component use memoization?

Answer: No. Use useMemo and useCallback when caching is useful for performance or stable identity, not automatically in every component.

Q10. Can Hooks be called inside conditions?

Answer: No. Hooks must be called at the top level of function components or custom Hooks so React can preserve their call order.


17. Practice Exercises

  1. Create a counter using useState.
  2. Build a multi-action counter using useReducer.
  3. Create a theme context and read it using useContext.
  4. Build a custom input that uses useRef to focus the input.
  5. Use useEffect to subscribe to a browser event and clean up the listener.
  6. Use useMemo to filter a large list of products.
  7. Use useCallback with a memoized child component and observe the rendering behavior.
  8. Create a custom Hook named useCounter that supports increment, decrement, and reset operations.
  9. Explain why a simple derived value often does not need useEffect.
  10. Choose an appropriate Hook for a form, modal, theme switcher, and complex shopping cart, and explain why.

18. Quick Revision Checklist

  • useState – local state that affects the UI.
  • useReducer – organized, complex state transitions.
  • useContext – reading shared context values.
  • useRef – DOM references and mutable values.
  • useEffect – synchronization with external systems.
  • useLayoutEffect – layout work before browser repaint.
  • useMemo – cached calculation results.
  • useCallback – cached function definitions.
  • useImperativeHandle – a custom ref handle.
  • Custom Hooks – reusable stateful logic.
  • useId – IDs for accessibility relationships.
  • useTransition and useDeferredValue – managing non-urgent UI updates.
  • Always follow the Rules of Hooks.

19. React JS Training Resources

Explore the following resources to learn React JS, understand Hooks, and practice building modern React applications.


20. Conclusion

Choosing the right React Hook begins with understanding what your component needs to do. Use useState for straightforward state, useReducer for complex transitions, useContext for shared context values, and useRef for references or mutable values that do not drive rendering. Use useEffect for external synchronization and performance Hooks only when they provide a useful benefit. Custom Hooks help you reuse logic across components. Selecting Hooks carefully makes React applications easier to understand, maintain, and improve.

whatsapp