Context API
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:
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> ); }
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
Create a New File: Inside the
context
folder, create a new file namedUserContextProvider.jsx
.Import React and
UserContext
:import React, { useState } from 'react'; import UserContext from './UserContext';
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
: TheUserContextProvider
component accepts a special prop calledchildren
. This is a generic term that refers to whatever components are wrapped inside theUserContextProvider
. For example, if you remember theOutlet
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 thechildren
.This way, all the components inside
UserContextProvider
will have access to the shared data (user
andsetUser
).
Providing a
value
: Thevalue
prop of theProvider
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 theuser
data but also update it usingsetUser
.Using
useState
: Since React requires state management, we use theuseState
hook to createuser
andsetUser
. 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:
Import the
UserContextProvider
:First, you need to import the
UserContextProvider
at the top of yourApp.jsx
orMain.jsx
file.import React from 'react'; import UserContextProvider from './context/UserContextProvider'; // Adjust the path if needed
Wrap Your Components with the
UserContextProvider
:In the return statement of your
App
orMain
component, wrap the components that need access to the context with theUserContextProvider
. 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
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
andpassword
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 theusername
andpassword
, 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
isnull
orundefined
, 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.
Subscribe to my newsletter
Read articles from Aaks directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by