React Context API: The Best Way to Manage Global State?

In React applications, state management is an important concept. When the process of passing props from parent to child components gets complex, which is known as props drilling, then you can use React Context API to make this process simpler.

In this post, you’ll learn:

  • Why state management is crucial in React.
  • What prop drilling is and its downsides.
  • How to use React Context API step by step.
  • Performance optimizations and best practices.

Before we get started, don’t forget to subscribe to my newsletter!
Get the latest tips, tools, and resources to level up your web development skills delivered straight to your inbox. Subscribe here!

Now let’s jump right into it!🚀

Why is State Management Important in React?

State management is important in React to keep different components in sync. In small applications, passing state from a parent component to a child component is simple. But as your app grows, you may need to share state between multiple components that are not related. This makes the process inefficient.

When state is passed at multiple levels inside a component, this is called props drilling.

For example:

const Parent = () => {
  const user = { name: "John" };
  return <Child user={user} />;
};

const Child = ({ user }) => {
  return <GrandChild user={user} />;
};

const GrandChild = ({ user }) => {
  return <p>Hello, {user.name}!</p>;
};

Problem:

  • The user is passed down three levels, even though only GrandChild needs it.
  • If more components are added, maintaining this structure becomes difficult.

Solution:

To avoid prop drilling, you can use React Context API, which provides a global store that any component can access directly. This will manage shared data efficiently.

What is React Context API?

React Context API is a built-in state management solution that provides a global store that you can access anywhere within the app and share state globally across components without passing props manually.

Why Use Context API?

Context API is useful because:

  • It avoids prop drilling.
  • This is a lightweight alternative to Redux.
  • Needs no extra set up as it is built in React.
  • Works well for small to medium-sized applications.

How to Use React Context API (Step-by-Step Guide)

Here’s a step-by-step guide to setting up context API:

Step 1: Create a Context

Create a new file UserContext.js and define a context.

import { createContext } from "react";

// Create Context 
export const UserContext = createContext();

Here,

  • createContext() creates a new global context.
  • This context will be used to share data between components.

Tip: Keep your context files in a separate folder within the app for code maintainability.

You might be wondering, “Do I need to memorize this Syntax?”

No worries! You can always refer back to this guide or the official docs whenever needed.

Step 2: Create a Context Provider

Create a context provider in the same file like this:

import { useState } from "react";

export function UserProvider({ children }) {
  const [user, setUser] = useState("John");

  return (
    <UserContext.Provider value={{ user, setUser }}>
      {children}
    </UserContext.Provider>
  );
}

Here,

  • useState(“John”) initializes state with the value John.
  • <UserContext.Provider> wraps the app and provides the user state.
  • The value prop makes user and setUser available throughout the app.

Now, your UserContext.js file will look like this:

import { createContext, useState } from "react";

export const UserContext = createContext();

export function UserProvider({ children }) {
  const [user, setUser] = useState("John");

  return (
    <UserContext.Provider value={{ user, setUser }}>
      {children}
    </UserContext.Provider>
  );
}

Step 3: Wrap the App in the Provider

Modify App.js to wrap the app with UserProvider.

import { UserProvider } from "./UserProvider";
import Home from "./Home";
import About from "./About";

function App() {
  return (
    <UserProvider>
      <Home />
      <About />
    </UserProvider>
  );
}

export default App;

Here,

  • The UserProvider wraps the entire application.
  • Now, Home and About can access the user state without prop drilling.

Step 4: Consume the Context in Components

(A) Read Context Data using useContext Hook

import { useContext } from "react";
import { UserContext } from "./UserContext";

function Home() {
  // The value of user is extracted using destructuring
  const { user } = useContext(UserContext);
  
  return <h1>Welcome, {user}!</h1>;
}

export default Home;

Here,

  • useContext(UserContext) gives access to the global state.
  • user is directly used in the component without passing props.

(B) Updating the State in Context

import { useContext } from "react";
import { UserContext } from "./UserContext";

function About() {
  const { user, setUser } = useContext(UserContext);

  return (
    <div>
      <h1>Current User: {user}</h1>
      <button onClick={() => setUser("David")}>Change User</button>
    </div>
  );
}

export default About;

Here, clicking the button updates the user globally across all components.

Performance Optimization

When the context state gets updated, then all of the connected components get re-rendered, which is unnecessary. To optimize this:

Use useMemo for Context Value

Wrap the state in useMemo to prevent re-renders unless the state actually changes.

const value = useMemo(() => ({ user, setUser }), [user]);

Complete example:

export function UserProvider({ children }) {
  const [user, setUser] = useState("John");

  const value = useMemo(() => ({ user, setUser }), [user]);

  return (
    <UserContext.Provider value={value}>
      {children}
    </UserContext.Provider>
  );
}

Without useMemo, every re-render of the provider will create a new object for value={{ user, setUser }}; this will cause all the components (which are using the user state) to re-render even if the user hasn’t changed.

Split Contexts for Better Performance

Instead of using a single context for everything, create multiple contexts for different concerns (e.g., user state, theme state).

For example:

<UserProvider>
  <ThemeProvider>
    <App />
  </ThemeProvider>
</UserProvider>

This prevents unnecessary updates when only one state changes.

How Does This Work?

  • UserProvider manages the user state (e.g., authentication, profile details).
  • ThemeProvider manages theme preferences (light/dark mode).
  • The App component and all its children can now access both user data and theme settings without prop drilling.

Creating Theme Context

import { createContext, useState } from "react";

export const ThemeContext = createContext();

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState("light");
  
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

Now, any component inside <ThemeProvider> can access and update the theme.

When to Use Context API?

  • When multiple components are sharing the same state.
  • To avoid prop drilling.
  • To avoid heavy setup of Redux.
  • When you want to manage a global state with a lightweight solution.
  • For managing themes, authentication, or language settings.

🎯Wrapping Up

That’s all for today!

I hope this post helps you.

If you found this post helpful, here’s how you can support my work:
Buy me a coffee – Every little contribution keeps me motivated!
📩 Subscribe to my newsletter – Get the latest tech tips, tools & resources.
𝕏 Follow me on X (Twitter) – I share daily web development tips & insights.

Keep coding & happy learning!

11 thoughts on “React Context API: The Best Way to Manage Global State?”

  1. It’s really useful. React context API is more clear now. Just have few questions on useMemo usage here. I think it’s brilliant but was thinking how it works here for objects.

    Reply

Leave a Comment