Context API: Global State Management in React

The React Context API provides a way to pass data through the component tree without having to pass props down manually at every level. It's designed for sharing data that can be considered "global" for a tree of React components, such as the authenticated user, theme, or preferred language.

Why Use Context API?

As applications grow, prop drilling (passing props down through many layers of components) can become cumbersome and error-prone. Context API offers a more elegant solution by allowing components to subscribe to context changes directly, bypassing intermediate components.

Core Concepts

The Context API consists of three main parts:

  1. `React.createContext()`: Creates a Context object. When React renders a component that subscribes to this Context object, it will read the current context value from the closest matching Provider above it in the tree.
  2. `Context.Provider`: A component that allows consuming components to subscribe to context changes. It accepts a value prop to be passed to consuming components that are descendants of this Provider.
  3. `Context.Consumer (or the useContext hook): A component that subscribes to context changes. It requires a render prop function as a child. The function receives the current context value as an argument and returns a React node.

Creating and Using Context

1. Creating the Context

First, you create a context object. This can be done in a separate file to keep your code organized.

src/contexts/AuthContext.js

import React from 'react';

// Default value is used only when a component is a direct descendant of the Provider
// and not a descendant of a Provider which is also a descendant of this component.
export const AuthContext = React.createContext({
    isAuthenticated: false,
    user: null,
    login: () => {},
    logout: () => {},
});

2. Providing the Context

Next, you wrap the part of your component tree that needs access to the context with the Provider component.

src/App.js

import React, { useState } from 'react';
import { AuthContext } from './contexts/AuthContext';
import UserProfile from './components/UserProfile';
import LoginButton from './components/LoginButton';

function App() {
    const [isAuthenticated, setIsAuthenticated] = useState(false);
    const [user, setUser] = useState(null);

    const login = (userData) => {
        setIsAuthenticated(true);
        setUser(userData);
    };

    const logout = () => {
        setIsAuthenticated(false);
        setUser(null);
    };

    const contextValue = {
        isAuthenticated,
        user,
        login,
        logout,
    };

    return (
        <AuthContext.Provider value={contextValue}>
            <div>
                <h1>My App</h1>
                <LoginButton />
                <UserProfile />
            </div>
        </AuthContext.Provider>
    );
}

export default App;

3. Consuming the Context (using useContext Hook)

The useContext hook is the modern and preferred way to consume context in functional components.

src/components/UserProfile.js

import React, { useContext } from 'react';
import { AuthContext } from '../contexts/AuthContext';

function UserProfile() {
    const { isAuthenticated, user } = useContext(AuthContext);

    if (!isAuthenticated) {
        return <p>Please log in.</p>;
    }

    return (
        <div>
            <h3>Welcome, {user?.name}!</h3>
            <p>Email: {user?.email}</p>
        </div>
    );
}

export default UserProfile;

src/components/LoginButton.js

import React, { useContext } from 'react';
import { AuthContext } from '../contexts/AuthContext';

function LoginButton() {
    const { isAuthenticated, login, logout } = useContext(AuthContext);

    const handleAuth = () => {
        if (isAuthenticated) {
            logout();
        } else {
            login({ name: 'Alice', email: 'alice@example.com' });
        }
    };

    return (
        <button onClick={handleAuth}>
            {isAuthenticated ? 'Logout' : 'Login'}
        </button>
    );
}

export default LoginButton;

Consuming Context with Context.Consumer (Older way)

While useContext is preferred, you might encounter or need to use Context.Consumer in older codebases or specific scenarios.

src/components/OldUserProfile.js

import React from 'react';
import { AuthContext } from '../contexts/AuthContext';

function OldUserProfile() {
    return (
        <AuthContext.Consumer>
            {({ isAuthenticated, user }) => (
                <>
                    {isAuthenticated ? (
                        <div>
                            <h3>Welcome, {user?.name}!</h3>
                            <p>Email: {user?.email}</p>
                        </div>
                    ) : (
                        <p>Please log in.</p>
                    )}
                </>
            )}
        </AuthContext.Consumer>
    );
}

export default OldUserProfile;

Best Practices and Considerations

The Context API is a powerful built-in solution for managing global state in React. It simplifies data sharing and avoids prop drilling, making your component tree cleaner and easier to maintain.

API Reference

React.createContext(defaultValue)

Creates a Context object. React will look for the nearest context provider above it when computing the context value.

Parameters:

  • defaultValue (any): The default value for the context. This will be the value returned by useContext or Context.Consumer if no Provider is found above the consuming component.

Returns:

An object with Provider and Consumer properties.

Context.Provider

Renders a Context Provider component that accepts a value prop to be passed to consuming components.

Props:

  • value (any): The value to be passed down to consuming components.

useContext(Context)

A hook that accepts a context object (the value returned from React.createContext) and returns the current context value for that context. The current context value is determined by the value prop of the nearest <Context.Provider> above the calling component in the tree.

Parameters:

  • Context (object): The Context object you want to consume.

Returns:

The current context value.

Context.Consumer

A component that subscribes to context changes. It requires a render prop function as a child. The function receives the current context value as an argument and returns a React node.

Props:

  • children (function): A render prop function that receives the context value.