DEV Community

Cover image for When to Use useState, useReducer, and useContext in React
Aneeqa Khan
Aneeqa Khan

Posted on

When to Use useState, useReducer, and useContext in React

State management finds the heart of development when it comes to making dynamic and responsive React applications. Besides the several hooks React has, the most generally used and practical are useState and useReducer; useContext, when regarded in general applications, comes on top.

Each serves unique purposes and has specific use cases that make it ideal for different scenarios. Let’s get into these three state hooks, know when to use, and find how these differ.


1. useState: The Simplicity Specialist

Overview

useState is the simplest and most commonly used React hook for state management. It lets you add and manage local state in a functional component without using class components.

Key Features

  • Ideal for simple, local states.
  • Provides a setter function to update state values.
  • Triggers re-render when the state is updated.

When to Use

  • For managing small and isolated state variables like form inputs, toggles, or counters.
  • When state updates are straightforward and don’t involve complex logic.

Example Usage

import React, { useState } from 'react';

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

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

export default Counter;
Enter fullscreen mode Exit fullscreen mode

Advantages

  • Simple and intuitive API.
  • Works well for local component state.

Limitations

  • It can become unwieldy when managing multiple related state variables.
  • It is not suitable for complex state transitions.

2. useReducer: The Logic Handler

Overview

useReducer is a more powerful alternative to useState for managing complex state transitions. It allows you to define state logic in a reducer function, similar to Redux.

Key Features

  • Uses a reducer function to handle state updates.
  • Ideal for scenarios where state transitions depend on the previous state.
  • Centralizes and organizes complex state logic.

When to Use

  • For managing complex or interrelated state variables.
  • When state updates involve multiple actions or transitions, like form validation, complex UI logic, or managing a to-do list.

Example Usage

import React, { 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:
      throw new Error('Unknown action type');
  }
}

const Counter = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

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

export default Counter;
Enter fullscreen mode Exit fullscreen mode

Advantages

  • Scales well for complex state management.
  • Makes state logic easier to test and debug.
  • Keeps state transitions predictable and centralized.

Limitations

  • More verbose than useState.
  • Adds complexity to simple state management tasks.

3. useContext: The Prop Drilling Savior

Overview

useContext is a hook designed to share data across components without passing props manually through each level of the component tree. It provides access to values stored in a React Context.

Key Features

  • Simplifies state sharing by eliminating prop drilling.
  • Works well for global or semi-global states like themes, user data, or settings.

When to Use

  • This is for sharing the state globally or across multiple components.
  • When you need a simple solution for avoiding prop drilling.

Example Usage

import React, { createContext, useContext } from 'react';

const ThemeContext = createContext();

const ThemedButton = () => {
  const theme = useContext(ThemeContext);
  return <button style={{ backgroundColor: theme }}>Click Me</button>;
};

const App = () => (
  <ThemeContext.Provider value="lightblue">
    <ThemedButton />
  </ThemeContext.Provider>
);

export default App;
Enter fullscreen mode Exit fullscreen mode

Advantages

  • Simplifies global state sharing.
  • Easy to implement for small-scale global states.

Limitations

  • It is not ideal for managing state updates or complex logic.
  • This can lead to performance issues if the entire tree re-renders due to frequent context updates.

Comparing the Three State Hooks

state hooks comparison


Using Hooks Together

Often, useReducer and useContext are used together to create scalable global state management systems. For instance, useReducer handles complex state transitions, while useContext provides global access to the state and dispatch function.

Example: Combining useReducer and useContext

import React, { createContext, useReducer, useContext } from 'react';

const CounterContext = createContext();

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:
      throw new Error('Unknown action');
  }
}

const CounterProvider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <CounterContext.Provider value={{ state, dispatch }}>
      {children}
    </CounterContext.Provider>
  );
};

const CounterDisplay = () => {
  const { state } = useContext(CounterContext);
  return <p>Count: {state.count}</p>;
};

const CounterControls = () => {
  const { dispatch } = useContext(CounterContext);
  return (
    <div>
      <button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
    </div>
  );
};

const App = () => (
  <CounterProvider>
    <CounterDisplay />
    <CounterControls />
  </CounterProvider>
);

export default App;
Enter fullscreen mode Exit fullscreen mode

How to Choose the Right Hook

  1. Start with useState:
    For most local state needs, useState is sufficient and straightforward.

  2. Upgrade to useReducer:
    If your state logic grows in complexity, switch to useReducer for better organization and scalability.

  3. Add useContext:
    When you need to share state globally or avoid prop drilling, combine useReducer with useContext.


Conclusion

React’s useState, useReducer, and useContext hooks each play a crucial role in state management. While useState keeps things simple, useReducer handles complexity, and useContext simplifies the sharing state. By understanding their unique strengths and use cases, you can easily build robust, maintainable applications.

Thank you for reading! Feel free to connect with me on LinkedIn or GitHub.

Top comments (0)