Welcome back! In the last few lessons, you learned how to set up an API client, build a login form, protect routes, and create a registration flow. Now, you are ready to make your authentication logic more organized and accessible throughout your React app.
In a real-world application, you often need to know whether a user is logged in or not in many different places — like the navigation bar, protected pages, or even when making API requests. If you try to pass this information down through props, your code can get messy and hard to manage. This is where React Context comes in. By using an authentication context, you can keep track of the user's login state and make it available anywhere in your app without having to pass it through every component.
To see why context helps, let’s look at a simple example. Imagine you have a navigation bar deep inside your app that needs to know whether the user is logged in and how to log them out.
❌ Bad Approach: Prop Drilling
Here, isAuthenticated and logout are passed through multiple components, even if only the deepest one needs them:
Notice how isAuthenticated and logout get passed through Layout even though Layout doesn’t use them. As your app grows, this “prop drilling” gets messy.
✅ Better Approach: Using Context
With AuthContext, you provide authentication state and actions once at the top level. Any component can access them directly without threading props everywhere.
React Context is a way to share data across your app without having to pass props down manually at every level. Think of it as a shared key that any component can use to check if the user is logged in, log them out, or update their authentication status.
For example, if you have a navigation bar and a protected page, both can use the same context to know if the user is authenticated. This keeps your code clean and avoids duplication.
When building a React app, different parts of your UI often need to know whether a user is logged in. For example, your navigation bar might need to show “Log out” instead of “Log in,” while your profile page needs access to the current user’s details. Without context, you would have to pass this information down as props through every intermediate component — a process called prop drilling. This quickly becomes messy, repetitive, and error-prone.
React Context solves this problem by acting as a shared store that any component can tap into, no matter how deep in the tree it is. With an AuthContext, we create one central place that holds authentication data and makes it available everywhere in the app.
-
What the AuthContext provides: At its core, the authentication context stores important state like
isAuthenticated, the user’s token, or even user profile info. Alongside the state, it also provides actions such aslogin(token)andlogout(). This makes it a single source of truth for authentication, so when the token changes, the rest of the app automatically knows whether the user is logged in or not. Typically, the token is also saved inlocalStorageso that the state can be restored even after a page refresh. -
Why this is better than props: Instead of manually passing
isAuthenticatedandlogoutdown through every layer of your app, any component can directly “subscribe” to the context. This keeps your code cleaner and makes it easier to maintain as your app grows.
To use AuthContext, the provider must wrap the entire subtree that calls useAuth()—that includes your routed pages and guards. The simplest, reliable setup is to wrap the RouterProvider with AuthProvider at the root:
- This guarantees that all routes, layout components, and utilities like
ProtectedRoutecan accessuseAuth(). - If
AuthProvideris mounted inside a page instead, anything outside that page (e.g., other routes or top-level nav) won’t see auth state. - Order matters only in terms of ancestry: the provider must be an ancestor of consumers. Wrapping
RouterProvideris the most straightforward way to ensure that.
Below is a minimal example for your entry point.
Why this placement works: Every routed screen and navigation element is now a child of AuthProvider, so is available everywhere—header, pages, protected routes, etc.
Let’s build the authentication context step by step. We’ll create a context, a provider, and a custom hook to use the context. We’ll also handle login and logout actions.
Here’s the main code for the authentication context:
Let’s break down what’s happening here:
AuthContext: This is the context object that will hold our authentication state and actions.AuthProvider: This component wraps your app and provides authentication state to all its children.- It uses
useStateto keep track of the authentication token. - The
useEffectsets up an interceptor so that every API request includes the token if it exists. - The
loginfunction saves the token, updates the state, and navigates to the user’s shelf. - The
logoutfunction removes the token, updates the state, and navigates to the home page.
- It uses
Now that you have the authentication context, let’s see how you can use it in your components. For example, you might want to show different navigation links depending on whether the user is logged in.
Here’s how you can use the context in your main app component:
Explanation:
- The
useAuthhook gives you access to and .
We previously built the login component. Everything should look familiar except the call to login(token)—that’s what wires the UI to the context you just created. Here is the full component you provided; we’ll annotate how useAuth().login is used.
How login(token) integrates
- After a successful POST to
/auth/login, we extract the token from the backend envelope. login(token):
In this lesson, you learned how to create an authentication context in React. You saw how to set up the context, provide it to your app, and use it in your components to manage login state and protect routes. This approach keeps your authentication logic organized and easy to use anywhere in your app.
- Provider placement: Wrap
RouterProviderwithAuthProviderat the root so every routed component can calluseAuth(). - Context design: Keep
AuthContextsmall and stable—isAuthenticated,login,logoutto minimize re-renders. - Consumption: Extract
{ isAuthenticated, logout }fromuseAuth()for conditional nav and use aProtectedRouteto guard screens. - Login integration: Call
login(token)after successful authentication to persist and broadcast auth state; the UI updates automatically.
You are now ready to practice using the authentication context in real code. In the next exercises, you will get hands-on experience with these concepts, such as updating navigation based on authentication and protecting routes. This will help you build more secure and user-friendly applications. Keep up the great work!
