Deciding Scope: Local vs. Global State

Learn the crucial difference between `useState` and the Context API to build clean, scalable React applications.

Lesson ProgressStep 1 of 9
App
Counter (count: 0)
Provider (value)
App
Counter
0 EXP

Welcome! Let's explore how React components manage data, or 'state'. We'll start with a component's private memory: local state.

// App.js

Local State: The `useState` Hook

Local state is data that is managed within a single component. It's the default and simplest way to handle state in React. You create it using the useState hook.

This state is completely private; no other component can see or modify it directly. It's perfect for things like:

  • The value of a form input.
  • Whether a dropdown or modal is open.
  • The status of a toggle switch.
const [name, setName] = useState('');
const [isOpen, setIsOpen] = useState(false);

System Check

Which of these is the BEST candidate for local state (`useState`)?

Advanced Holo-Simulations

0 EXP

Log in to unlock these advanced training modules and test your skills.


Achievements

📦
State Keeper

Correctly use `useState` for a component's local state.

P
Prop Driller

Identify the problem of prop drilling and lift state to a common ancestor.

🌐
Context Whiz

Refactor a prop-drilling problem using the Context API.

Mission: Refactor Prop Drilling

The component below is passing the `username` prop down three levels. Refactor this code using the Context API to provide the `username` directly to `UserAvatar`.

A.D.A. Feedback:

> System integrity looks stable. Code is valid.

Challenge: Match the Concept

Drag the concepts (left) to match their correct definitions or use cases (right).

Context API
useState
Prop Drilling
A single component's internal data (e.g., a form input).
App-wide user information or theme.
The problem of passing props down many levels.

Challenge: Complete the Context API

Fill in the missing function/component names to make the Context API work.

const ThemeContext = (null);
<ThemeContext. value="dark">
const theme = (ThemeContext);

Consult A.D.A.

Community Holo-Net

Peer Project Review

Submit your "Context Refactor" project for feedback from other Net-Runners.

The Complete Guide to React State Management

"State" is the data that determines how your component renders and behaves. It's the memory of your application. Managing this state is arguably the most critical task in building a React application. A good state management strategy leads to a clean, maintainable, and performant app. A poor one leads to a tangled mess of bugs and unpredictable behavior.

This guide will walk you through the entire spectrum of state management in React, from the simplest component-level state to complex, application-wide global solutions.

1. The Foundation: Local State with `useState`

**Always start here.** Local state is state that is "owned" by a single component. The `useState` hook is your primary tool for this.

  • What it is: A hook that lets you add state to functional components.
  • When to use it: Any data that only affects this one component and its direct children. Examples:
    • The value of a form input.
    • Whether a modal or dropdown is open.
    • The "on/off" status of a toggle switch.
function ToggleButton() {
  // 'isOpen' is local state. Only ToggleButton knows about it.
  const [isOpen, setIsOpen] = useState(false);

  return (
    <button onClick={() => setIsOpen(!isOpen)}>
      {isOpen ? 'ON' : 'OFF'}
    </button>
  );
}

2. For Complex Local State: `useReducer`

Sometimes, a component's state logic gets complicated. You might have many `useState` calls that all update at the same time, or the next state might depend on the previous one in a complex way. For this, `useReducer` is a better, more organized alternative.

  • What it is: A hook for managing state with a reducer function, similar to the Redux pattern.
  • When to use it:
    • When you have complex state logic with many moving parts.
    • When the next state depends on the previous state.
    • To optimize performance by passing a `dispatch` function down instead of many state-setting callbacks.
const initialState = { count: 0, step: 1 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + state.step };
    case 'setStep':
      return { ...state, step: action.payload };
    default:
      throw new Error();
  }
}

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

  return (
    <button onClick={() => dispatch({ type: 'increment' })}>
      {state.count}
    </button>
  );
}

3. Sharing State: Lifting State Up

What happens when two components need to share the same state? For example, a filter component needs to tell a list component what to display.

The simplest React pattern is **"Lifting State Up."** You find the closest common ancestor (parent) of both components, move the state and setter function to that parent, and then pass the state and the function down as props.

✔️ Good Practice

function App() {
  const [filter, setFilter] = useState('all');

  return (
    <>
      {/* Parent passes state and setter down */}
      <FilterControls currentFilter={filter} onFilterChange={setFilter} />
      <TodoList filter={filter} />
    </>
  );
}

4. The Problem: Prop Drilling

Lifting state up works, but it has a major downside. What if the common parent is 5 levels above the component that needs the data? You have to pass the prop through 4 intermediate components that don't care about it. This is **prop drilling**.

❌ Bad Practice

// username prop is "drilled" through Page and Header
function App() {
  return <Page username="test" />;
}
function Page({ username }) {
  return <Header username={username} />;
}
function Header({ username }) {
  return <Avatar username={username} />;
}
function Avatar({ username }) {
  return <span>{username}</span>;
}

Prop drilling is bad because it makes components hard to refactor and tightly couples them to a specific structure.

5. The Built-in Solution: Context API

The Context API is React's built-in solution for prop drilling. It allows you to create a "global" piece of state that any component in the tree can access *without* receiving it as a prop.

  1. Create Context: Use `createContext` to create a context object.
  2. Provide Value: Wrap a part of your tree (like the whole app) in the `MyContext.Provider` component and pass it a `value`.
  3. Consume Value: Any component *inside* that provider can now call `useContext(MyContext)` to get the value.
// 1. Create Context
const UserContext = createContext(null);

function App() {
  // 2. Provide Value
  return (
    <UserContext.Provider value="test">
      <Page />
    </UserContext.Provider>
  );
}

// Page and Header are no longer involved
function Page() {
  return <Header />;
}
function Header() {
  return <Avatar />;
}

function Avatar() {
  // 3. Consume Value
  const username = useContext(UserContext);
  return <span>{username}</span>;
}

The Big Gotcha: Context is not a performance silver bullet. When the value in a `Provider` changes, **all components** that call `useContext` for that context will re-render, even if they only care about a small part of the value. For high-frequency updates, this can be slow.

6. Beyond Context: Global State Libraries

When your application state becomes very complex (e.g., an e-commerce cart, a social media feed, a complex dashboard), you may outgrow Context. This is where dedicated state management libraries shine.

  • Redux (with Redux Toolkit): The classic. Redux provides a single, predictable state container. With Redux Toolkit, it's much easier to use. It's excellent for large teams, complex state, and when you need powerful tools like middleware (for logging, API calls) and time-travel debugging.
  • Zustand: A modern, minimalist alternative. It's very fast, has almost no boilerplate, and feels more "React-like" than Redux. It solves the Context performance problem by allowing components to subscribe to only the *slices* of state they care about.
  • Recoil / Jotai: These libraries use an "atomic" model, where state is broken into tiny, independent pieces ("atoms"). This can be very intuitive and performant.
Key Takeaway: The State Management Philosophy
  1. Start with `useState` for all local state.
  2. When siblings need to share state, lift state up to the common parent.
  3. If you are prop drilling 3+ levels deep, use Context API for low-frequency, stable data (like theme, auth).
  4. If Context causes performance issues or state logic is complex, adopt a dedicated library like Zustand or Redux.

React State Management Glossary

State
A JavaScript object that holds data to control a component's behavior and rendering. When state changes, React re-renders the component.
Local State
State that is owned and managed by a single component. Created with `useState` or `useReducer`.
`useState`
The primary React hook for adding local state to a functional component. Returns an array with the current state value and a function to update it.
`useReducer`
A hook for managing more complex local state. It takes a reducer function and an initial state, and returns the current state and a `dispatch` function.
Lifting State Up
A pattern for sharing state between sibling components by moving the state to their closest common ancestor and passing it down via props.
Prop Drilling
The (undesirable) practice of passing props through many levels of intermediate components that do not need the data, just to get it to a deeply nested child.
Global State
State that is accessible by any component in the application, regardless of its position in the component tree.
Context API
React's built-in mechanism for solving prop drilling. It allows you to "provide" a value at the top of the tree and "consume" it in any child component.
`createContext`
The function used to create a new Context object.
`Provider`
The component (`MyContext.Provider`) that wraps a part of the component tree and makes the context's `value` available to all descendants.
`useContext`
The hook used inside a component to read the current value from a Context provider.
Redux
A popular and powerful third-party library for managing complex global state using a single "store" and a strict update pattern (actions and reducers).
Zustand
A lightweight, modern global state management library that is known for its simplicity, minimal boilerplate, and high performance.

About the Author

Author's Avatar

TodoTutorial Team

Passionate developers and educators making programming accessible to everyone.

This article was written and reviewed by our team of senior React developers, who have built and scaled applications using every state management pattern described here.

Verification and Updates

Last reviewed: October 2025.

We strive to keep our content accurate and up-to-date. This tutorial is based on React 18+ and reflects modern best practices, including the role of server components and hooks.

External Resources

Found an error or have a suggestion? Contact us!