L1: Create AuthProvider Component
Build the foundation for authentication with Context API
Let's build the foundation for authentication by creating an AuthProvider component using React's Context API! 🏗️
Why We Need AuthProvider
Right now, our app has no way to:
- Know if a user is signed in
- Share authentication state across components
- Protect routes that require authentication
- Add auth tokens to API requests
The solution: Create a global authentication context that wraps our entire app and provides auth state to any component that needs it.
What is Context API?
Create AuthProvider Component
Create a new file for the authentication provider:
import { createContext, useContext, useState } from 'react';
// 1. Create Auth Context
const AuthContext = createContext(undefined);
// 2. Create useAuth Hook
export const useAuth = () => {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};
// 3. Create AuthProvider Component
export const AuthProvider = ({ children }) => {
// Authentication state
const [user, setUser] = useState(null);
const [token, setToken] = useState(null);
const [isLoading, setIsLoading] = useState(true);
// Sign in function
const signIn = async (email, password) => {
// TODO: Implement in later lesson
console.log('signIn called:', email);
};
// Sign out function
const signOut = () => {
setUser(null);
setToken(null);
};
// Context value provided to children
const value = {
user,
token,
isLoading,
signIn,
signOut,
};
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
};Understanding the Code
Creating the context:
const AuthContext = createContext(undefined);Why undefined as default?
This forces us to use the hook correctly. If someone tries to use useAuth() outside of <AuthProvider>, they'll get:
const context = useContext(AuthContext); // undefined
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}Better than null because:
nullcould be a valid state (no user)undefinedclearly means "not wrapped in provider"- Catches setup errors early
Three pieces of state:
const [user, setUser] = useState(null);
const [token, setToken] = useState(null);
const [isLoading, setIsLoading] = useState(true);Why each one:
1. user (null | object)
// null = not signed in
user = null
// object = signed in
user = {
id: '123',
name: 'John Doe',
email: 'john@example.com'
}2. token (null | string)
// null = no auth token
token = null
// string = has auth token
token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'3. isLoading (boolean)
// true = checking for existing session
isLoading = true // On app start
// false = done checking
isLoading = false // After checking tokenWhy start with isLoading: true?
When the app loads, we need to check if the user has an existing session (stored token). During this check, we show a loading state. Once the check completes, we set isLoading to false.
Custom hook for accessing context:
export const useAuth = () => {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};Why this pattern?
1. Error handling:
// ❌ Without hook:
function Component() {
const auth = useContext(AuthContext);
// If outside provider, auth is undefined
// Might cause runtime errors later
}
// ✅ With hook:
function Component() {
const auth = useAuth();
// Throws error immediately if outside provider
// Catches mistakes during development
}2. Cleaner imports:
// ❌ Without hook:
import { useContext } from 'react';
import { AuthContext } from './AuthProvider';
const auth = useContext(AuthContext);
// ✅ With hook:
import { useAuth } from './AuthProvider';
const auth = useAuth();3. Abstraction:
If we change the internal implementation, components using useAuth() don't need updates.
Provider component wraps children:
export const AuthProvider = ({ children }) => {
// State and functions...
const value = {
user,
token,
isLoading,
signIn,
signOut,
};
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
};Key points:
1. children prop:
<AuthProvider>
<App /> {/* This is children */}
</AuthProvider>2. value object:
Everything in value is accessible via useAuth():
const { user, token, signIn, signOut } = useAuth();3. Provider renders children:
{children} // Renders wrapped componentsFlow:
AuthProvider
→ Manages auth state
→ Provides value to children
→ Children can access via useAuth()Placeholder Functions
Notice the placeholder functions:
const signIn = async (email, password) => {
// TODO: Implement in later lesson
console.log('signIn called:', email);
};
const signOut = () => {
setUser(null);
setToken(null);
};Why not implement now?
We're building incrementally:
- Lesson 1 - Create provider structure
- Lesson 2 - Add to app
- Lesson 3 - Fetch token on mount
- Later - Full sign-in implementation
This approach helps you understand each piece before combining them.
Testing the Component
Even though it's not connected yet, let's verify it compiles:
Check file structure:
src/
├── components/
│ └── AuthProvider.jsx ← New file!Verify imports:
Make sure all imports are correct:
createContextfrom 'react'useContextfrom 'react'useStatefrom 'react'
Check exports:
File should export:
useAuthhookAuthProvidercomponent
What's Next?
In the next lesson, we'll:
- Add
AuthProviderto wrap our entire app - Make auth state available to all components
- Test that
useAuth()hook works
✅ Lesson Complete! You've created the AuthProvider component with Context API!
Key Takeaways
- ✅ Context API provides global state without prop drilling
- ✅ Three-part pattern: Context, Provider, Custom Hook
- ✅ AuthProvider manages user, token, and loading state
- ✅ useAuth hook provides safe access to auth context
- ✅ Error handling catches misuse during development
- ✅ Placeholder functions prepare for future implementation