Context API in React.js

React is a powerful JavaScript library for building user interfaces, and one of its core principles is the unidirectional data flow. However, as applications grow, managing state and passing data through the component tree can become cumbersome. This is where the Context API comes into play.

Introduction to Context API

The Context API in React provides a way to share values between components without explicitly passing props through every level of the component tree. It is particularly useful for global state management, such as theme settings, authenticated user details, or application settings.

What is Context API?

The Context API allows you to create a context object, which holds the global data. Components that need to access this data can subscribe to the context and retrieve the values directly.

Why Use Context API?

  • Prop Drilling Solution: It solves the problem of prop drilling where you need to pass props down multiple levels of the component tree.
  • Global State Management: It helps manage global states that are needed across various components in an application.
  • Cleaner Code: It results in cleaner and more maintainable code by reducing the number of props passed down.

Creating and Using Context

Let’s dive into the steps to create and use context in a React application.

Step 1: Creating a Context

To create a context, use the createContext method from React

				
					import React, { createContext } from 'react';

const MyContext = createContext();

				
			

This code creates a context object called MyContext.

Step 2: Providing Context

The context provider is a component that uses the Provider component from the created context to supply the context value to its children.

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

// Create a Context
const MyContext = createContext();

const MyProvider = ({ children }) => {
    const [value, setValue] = useState("Hello, World!");

    return (
        <MyContext.Provider value={{ value, setValue }}>
            {children}
        </MyContext.Provider>
    );
};

export default MyProvider;

				
			

Here, MyProvider is a component that wraps its children with the MyContext.Provider. The value prop of the provider holds the context data.

Step 3: Consuming Context

To consume the context in a component, use the useContext hook.

				
					import React, { useContext } from 'react';
import MyProvider, { MyContext } from './MyProvider';

const MyComponent = () => {
    const { value, setValue } = useContext(MyContext);

    return (
        <div>
            <p>{value}</p>
            <button onClick={() => setValue("New Value!")}>Change Value</button>
        </div>
    );
};

const App = () => (
    <MyProvider>
        <MyComponent />
    </MyProvider>
);

export default App;

				
			

In MyComponent, we use the useContext hook to access the value and setValue from MyContext. The App component wraps MyComponent with MyProvider to provide the context.

Output:

When you run the above code, the initial output will be:

				
					Hello, World!

				
			

When you click the button, it will change to:

				
					New Value!

				
			

Advanced Usage of Context API

Let’s explore some advanced concepts and patterns for using the Context API.

Context with Multiple Providers

You can use multiple contexts by nesting providers.

				
					const FirstContext = createContext();
const SecondContext = createContext();

const FirstProvider = ({ children }) => {
    const [firstValue, setFirstValue] = useState("First");

    return (
        <FirstContext.Provider value={{ firstValue, setFirstValue }}>
            {children}
        </FirstContext.Provider>
    );
};

const SecondProvider = ({ children }) => {
    const [secondValue, setSecondValue] = useState("Second");

    return (
        <SecondContext.Provider value={{ secondValue, setSecondValue }}>
            {children}
        </SecondContext.Provider>
    );
};

const NestedProviders = ({ children }) => (
    <FirstProvider>
        <SecondProvider>
            {children}
        </SecondProvider>
    </FirstProvider>
);

const MyComponent = () => {
    const { firstValue, setFirstValue } = useContext(FirstContext);
    const { secondValue, setSecondValue } = useContext(SecondContext);

    return (
        <div>
            <p>{firstValue}</p>
            <p>{secondValue}</p>
            <button onClick={() => setFirstValue("Updated First")}>Update First</button>
            <button onClick={() => setSecondValue("Updated Second")}>Update Second</button>
        </div>
    );
};

const App = () => (
    <NestedProviders>
        <MyComponent />
    </NestedProviders>
);

export default App;

				
			

Output:

Initially, the output will be:

				
					First
Second

				
			

After clicking the buttons, it will change to:

				
					Updated First
Updated Second

				
			

Dynamic Context Values

You can pass dynamic values to the context provider.

				
					const DynamicProvider = ({ children }) => {
    const [theme, setTheme] = useState('light');
    const toggleTheme = () => setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light'));

    return (
        <ThemeContext.Provider value={{ theme, toggleTheme }}>
            {children}
        </ThemeContext.Provider>
    );
};

const ThemeComponent = () => {
    const { theme, toggleTheme } = useContext(ThemeContext);

    return (
        <div style={{ background: theme === 'light' ? '#fff' : '#333', color: theme === 'light' ? '#000' : '#fff' }}>
            <p>Current Theme: {theme}</p>
            <button onClick={toggleTheme}>Toggle Theme</button>
        </div>
    );
};

const App = () => (
    <DynamicProvider>
        <ThemeComponent />
    </DynamicProvider>
);

export default App;

				
			

Output:

Initially, the output will be a light-themed UI:

				
					Current Theme: light

				
			

After clicking the “Toggle Theme” button, it will switch to a dark-themed UI:

				
					Current Theme: dark

				
			

Best Practices for Using Context API

  • Avoid Overusing Context: Context is powerful but should be used wisely. For local state, continue using component-level state.
  • Memoize Context Values: To prevent unnecessary re-renders, memoize the values passed to the provider using useMemo.
  • Split Contexts: Split contexts based on concerns to avoid a single context becoming too large and complex.
  • Custom Hooks: Create custom hooks to encapsulate context logic, making it easier to reuse and manage.
				
					const useTheme = () => {
    const context = useContext(ThemeContext);
    if (!context) {
        throw new Error('useTheme must be used within a ThemeProvider');
    }
    return context;
};

// Usage in a component
const MyComponent = () => {
    const { theme, toggleTheme } = useTheme();

    return (
        <div>
            <p>Current Theme: {theme}</p>
            <button onClick={toggleTheme}>Toggle Theme</button>
        </div>
    );
};

				
			

Example 1: Theme Switcher Application

In this example, we’ll build a simple application that allows users to switch between light and dark themes.

Step-by-Step Implementation

Step 1: Create the Context

First, create a new context for the theme.

				
					// ThemeContext.js
import React, { createContext, useState, useContext } from 'react';

const ThemeContext = createContext();

export const useTheme = () => {
    return useContext(ThemeContext);
};

export const ThemeProvider = ({ children }) => {
    const [theme, setTheme] = useState('light');

    const toggleTheme = () => {
        setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light'));
    };

    return (
        <ThemeContext.Provider value={{ theme, toggleTheme }}>
            {children}
        </ThemeContext.Provider>
    );
};

				
			

Step 2: Create the Theme Component

Create a component that consumes the theme context.

				
					// ThemeComponent.js
import React from 'react';
import { useTheme } from './ThemeContext';

const ThemeComponent = () => {
    const { theme, toggleTheme } = useTheme();

    const styles = {
        backgroundColor: theme === 'light' ? '#fff' : '#333',
        color: theme === 'light' ? '#000' : '#fff',
        padding: '20px',
        textAlign: 'center',
    };

    return (
        <div style={styles}>
            <p>Current Theme: {theme}</p>
            <button onClick={toggleTheme}>Toggle Theme</button>
        </div>
    );
};

export default ThemeComponent;

				
			

Step 3: Create the App Component

Finally, set up the App component to use the ThemeProvider

				
					// App.js
import React from 'react';
import { ThemeProvider } from './ThemeContext';
import ThemeComponent from './ThemeComponent';

const App = () => {
    return (
        <ThemeProvider>
            <ThemeComponent />
        </ThemeProvider>
    );
};

export default App;

				
			

Output

When you run this application, you’ll see a page with a white background and black text (light theme). Clicking the “Toggle Theme” button will switch to a dark theme with a black background and white text.

Example 2: User Authentication System

In this example, we’ll build a basic authentication system where users can log in and log out. The authentication status will be shared across the application using the Context API.

Step-by-Step Implementation

Step 1: Create the Context

First, create a new context for authentication.

				
					// AuthContext.js
import React, { createContext, useState, useContext } from 'react';

const AuthContext = createContext();

export const useAuth = () => {
    return useContext(AuthContext);
};

export const AuthProvider = ({ children }) => {
    const [user, setUser] = useState(null);

    const login = (username) => {
        setUser({ name: username });
    };

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

    return (
        <AuthContext.Provider value={{ user, login, logout }}>
            {children}
        </AuthContext.Provider>
    );
};

				
			

Step 2: Create the Auth Component

Create a component that consumes the authentication context.

				
					// AuthComponent.js
import React from 'react';
import { useAuth } from './AuthContext';

const AuthComponent = () => {
    const { user, login, logout } = useAuth();

    return (
        <div style={{ padding: '20px', textAlign: 'center' }}>
            {user ? (
                <>
                    <p>Welcome, {user.name}!</p>
                    <button onClick={logout}>Logout</button>
                </>
            ) : (
                <>
                    <p>Please log in.</p>
                    <button onClick={() => login('John Doe')}>Login</button>
                </>
            )}
        </div>
    );
};

export default AuthComponent;

				
			

Step 3: Create the App Component

Finally, set up the App component to use the AuthProvider.

				
					// App.js
import React from 'react';
import { AuthProvider } from './AuthContext';
import AuthComponent from './AuthComponent';

const App = () => {
    return (
        <AuthProvider>
            <AuthComponent />
        </AuthProvider>
    );
};

export default App;

				
			

Output

When you run this application, you’ll initially see a message asking you to log in. After clicking the “Login” button, the message will change to welcome the user, and a “Logout” button will appear. Clicking the “Logout” button will revert the state back to the initial login prompt.

The Context API is a valuable tool for managing global state in React applications. By understanding how to create, provide, and consume context, you can simplify state management and reduce the complexity of prop drilling. While it's a powerful feature, it's important to use it judiciously and follow best practices to maintain performance and code maintainability. Happy coding !❤️

Table of Contents