Context API

AaksAaks
9 min read

On the 13th day of my learning journey, I explored how to capture and manage data in React using the Context API. I’m thrilled to share my insights today. Let’s start from the basics, let’s dive in!

Imagine you need to pass data from the <App> component to a <Card> component. If there was no <Dashboard> in between, and we only had <App> and <Card>, it would be simple. You could directly pass the data as props.

function App() {
  return (
    <Card title="hero" />
  );
}

But what if the username is fetched in <App> from a database, and now we want to access it in <Card>, which is nested inside another component called <Dashboard>?

Initially, the components might look like this:

<Dashboard title="hero"> 
  <LeftSide /> 
  <RightSide /> 
  <RightTop />
  <Card />
</Dashboard>

However, the <Card> doesn't directly receive the data. To pass the data from <App> to <Card>, it needs to go through multiple levels.

If the <Card> component is nested deeply inside other components, and you need to pass data from <App> (where the data is fetched) to <Card>, you would end up passing props down multiple levels, which is called prop drilling. For example:

If <Card> needs data from <App>, and <App> can only pass it to <Dashboard>, then:

  • <Dashboard> would need to pass the data down to <LeftSide> or <RightSide> or <RightTop> even if they don’t need it.

  • Eventually, it would be passed down to <Card>.

Better Approach: Context API

Instead of drilling props down through multiple components, we can use a global object like:

{
  title: "hero"
}

With this, <Card> can directly access the data from the global object without needing it to be passed through <Dashboard> or other components. This way, <App> can provide the data directly to any component that needs it, regardless of how deeply nested it is.

This is where the Context API comes in. It allows us to avoid unnecessary prop drilling by letting components access shared data directly, no matter where they are in the component tree.

There are other libraries for managing state in React, such as Redux (React-Redux), Zustand, and more. These libraries also manage state efficiently and help pass data in a structured way. However, the Context API is a built-in solution for React.

Using Context API in a Project

The Context API can be integrated at the beginning or later on in a project. If a project is complex or follows best practices, it’s common to see the Context API used extensively.

After understanding the basics of the Context API and how it helps avoid prop drilling, I created a project using Vite to practice the concept.

Handling Context API

Using the Context API involves creating a global state, where you can store all the shared data. However, because it's React, you might find that updating the state across different components isn’t always straightforward.

One initial idea might be to create a single file like global.jsx and keep all global variables there. But there's a problem with this approach—any state update would trigger re-renders across all components, even those that don’t need the updated data. This can lead to unnecessary performance issues.

To avoid this, I organized my project by creating a folder named context, and inside it, I added a file called UserContext.js. (not .jsx) A similar pattern for other contexts like login, products, carts, etc can be followed.

In UserContext.js

import React from 'react';

const UserContext = React.createContext();

export default UserContext;

The createContext() method is a built-in function in React that allows you to create a context. At this point we care clear that, A context is essentially a way to share data across multiple components without having to pass props down manually at every level.

When we create a context, we define a global variable (like UserContext) that can be used to manage data across multiple components without passing it as props at every level.


Creating a UserContextProvider

Now that we've set up the UserContext, the next step is to create a Provider. There are two approaches:

  1. Directly set up the Provider in App.jsx.

     import UserContext from './context/UserContext'; 
     function App() {
      return (
    
      <UserContext.Provider value={{ /* some shared state / }}>
      {/ Components like Card, Login, etc., go here */} 
     </UserContext.Provider>
    
      ); }
    
  2. Create a separate UserContextProvider.jsx file.

We'll start by following the second approach, where we create a separate file for the Provider.

Step-by-Step: Creating UserContextProvider.jsx

  1. Create a New File: Inside the context folder, create a new file named UserContextProvider.jsx.

  2. Import React and UserContext:

     import React, { useState } from 'react';
     import UserContext from './UserContext';
    
  3. Define the UserContextProvider Component:

     const UserContextProvider = () => { 
     };
    

What the final code will look like:

import React, { useState } from 'react';
import UserContext from './UserContext';

const UserContextProvider = ({ children }) => {
  // State management
  const [user, setUser] = useState(null);

  // Provide the data to all wrapped components
  return (
    <UserContext.Provider value={{ user, setUser }}>
      {children}
    </UserContext.Provider>
  );
};

export default UserContextProvider;

Explanation:

  • Using children: The UserContextProvider component accepts a special prop called children. This is a generic term that refers to whatever components are wrapped inside the UserContextProvider. For example, if you remember the Outlet concept from React Router, this is similar, children will represent anything passed into this provider.

  • Returning UserContext.Provider:

    • We use the UserContext.Provider to wrap the children.

    • This way, all the components inside UserContextProvider will have access to the shared data (user and setUser).

  • Providing a value: The value prop of the Provider allows you to pass the data that should be shared. Instead of directly passing a single value, we pass an object containing { user, setUser }. This allows components to not only access the user data but also update it using setUser.

  • Using useState: Since React requires state management, we use the useState hook to create user and setUser. This lets us easily manage and update the user data across different components.

Providing Access to UserContext

Now that we have set up the UserContext and the UserContextProvider, we need to give access to this context in our main application file, which could be App.jsx or Main.jsx. Here's how to do it:

  1. Import the UserContextProvider:

    First, you need to import the UserContextProvider at the top of your App.jsx or Main.jsx file.

     import React from 'react';
     import UserContextProvider from './context/UserContextProvider'; // Adjust the path if needed
    
  2. Wrap Your Components with the UserContextProvider:

    In the return statement of your App or Main component, wrap the components that need access to the context with the UserContextProvider. This ensures that any component within this provider can access the user data.

     function App() {
       return (
         <UserContextProvider>
           {/* All components that need access to UserContext go here */}
         </UserContextProvider>
       );
     }
    
     export default App; // Export the App component
    
  3. Components Inside the Provider:

    Now, any component nested within the UserContextProvider, can access the context data.

Creating the Login Component

Let's break down the process of creating a Login component step by step.

Step 1: Setting Up the Component

Create a file named Login.jsx and start by importing React and necessary hooks.

import React, { useState, useContext } from 'react';
import UserContext from '../context/UserContext'; // Import UserContext

Step 2: Initialize State

We need to manage the state for the username and password that the user will enter. We'll use the useState hook for this.

function Login() {
  const [username, setUsername] = useState(''); // State for username
  const [password, setPassword] = useState(''); // State for password
  • Here, we initialize username and password as empty strings. This allows us to have controlled input fields.

Step 3: Accessing the User Context

Next, we'll access the UserContext using the useContext hook. This will allow us to update the user state in the context provider.

const { setUser } = useContext(UserContext); // Access setUser from UserContext
  • The setUser function will be used to update the user data in the context when the form is submitted.

Step 4: Creating the Form Structure

Now we will create the form structure where users can input their credentials.

return (

    <div>
        <h2> Login </h2>

        <input type="text"
        value={username}
        onChange={(e) => setUsername(e.target.value)}
        placeholder='usename' />
        <input type="text"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder='password' />
        <button onClick={handleSubmit}>Submit</button>

    </div>

  )

Step 5: Handling Form Submission

We need to define the handleSubmit function that will execute when the form is submitted.

const handleSubmit = (e) => {
    e.preventDefault(); // Prevent the default form submission behavior
    setUser({ username, password }); // Update the user context with the input values
  };
  • Preventing Default Behavior: e.preventDefault() is crucial to stop the default form submission process, which would otherwise refresh the page.

  • Updating Context: We call setUser with an object containing the username and password, which updates the user context.

Creating the Profile Component

The Profile component will display the username of the logged-in user or prompt them to log in if no user is found.

Step 1: Setting Up the Component

Create a file named Profile.jsx and start by importing React and the necessary hooks.

import React, { useContext } from 'react';
import UserContext from '../context/UserContext'; // Import UserContext

Step 2: Accessing User Data from Context

Within the Profile component, we'll access the user data from the context using the useContext hook.

function Profile() {
    const { user } = useContext(UserContext); // Access user from UserContext
  • Here, we're destructuring user from the context, which allows us to access the logged-in user's information.

Step 3: Conditional Rendering

Next, we will add a conditional check to see if the user is logged in or not.

if (!user) return <div>Please login</div>; // Prompt to log in if no user is found
  • If user is null or undefined, we display a message prompting the user to log in.

Step 4: Displaying User Information

If the user is logged in, we will display a welcome message that includes their username.

return <div>Welcome {user.username}</div>; // Welcome message for logged-in user
}

Final Step: Exporting the Component

Finally, export the Profile component for use in other parts of the application.

export default Profile;

Integrating the Profile Component in App.jsx

Now, let's see how the Profile component fits into your App structure.

import Login from './components/Login'; // Import Login component
import Profile from './components/Profile'; // Import Profile component
import UserContextProvider from './context/UserContextProvider'; // Import context provider

function App() {
  return (
    <UserContextProvider>
      <h1>Hello</h1>
      <Login />  {/* Login component */}
      <Profile /> {/* Profile component */}
    </UserContextProvider>
  );
}

Context Provider: By wrapping Login and Profile within UserContextProvider, both components can access the shared context, allowing Login to update the user state and Profile to display the current user information.

With this we were able to receive and send data using Context API.


In this blog, we explored the React Context API to manage state effectively across components. By creating a UserContext, we demonstrated how to share user data between a Login component and a Profile component without prop drilling. This was just for the sake of understanding, I am excited to implement this further.

0
Subscribe to my newsletter

Read articles from Aaks directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Aaks
Aaks