PresentationInteractive, narrated presentation of the section content.
ContentDetailed description of the section content.
Understanding Context API in React
React's Context API allows you to share state (or values) across components without prop drilling. Prop drilling happens when you pass props through multiple nested components to get to a child. Imagine the following application where we use logged-in user info to display content:
import React, { render } from 'react';
function UserInfo({ user }: { user: User }) {
return <span>{user.name} / {user.uid}</span>
}
function UserPosts({ user }: { user?: User }) {
return <ul>
{user.posts.map(i => (
<li key={i.id}>{i.title}</li>
))}
</ul>
}
function UserDetails({ user }) {
return (
<>
<UserInfo user={user} />
<UserPosts user={user} />
</>
)
}
export default function App() {
const user = {
name: "Tomas",
uid: 1,
posts: [
{ id: 1, title: "Post 1" },
{ id: 2, title: "Post 2" },
]
}
return (
<UserDetails user={user} />
);
}
render(<App />)Every time we want to use user in our application, we would have to provide the user as a prop of the child component. You can see that this can quickly become complicated. For example when you are dealing with themes or localisation you do not want to provide theme and language to every component. With Context API, you can avoid this by making data globally accessible to components that need it.
How Context API Works
- Create a Context: This acts as the "container" for the shared state.
- Provide the Context: Use the
Providerto wrap components that need access to the shared state. - Consume the Context: Use
useContextto access the shared values in a component.
Simplifying Context with Custom Providers and Hooks
To make using Context simpler and reusable, we’ll create:
- A Custom Context Provider to encapsulate logic.
- A Custom Hook for accessing context values.
Step 1: Traditional Context Setup
Before simplifying, here's the traditional way to use Context:
import React, { createContext, useState, useContext } from "react";
// Step 1: Create a Context
const UserContext = createContext();
// Step 2: Provide the Context
export const UserProvider = ({ children }) => {
const [user, setUser] = useState("John Doe");
return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
};
// Step 3: Consume the Context
const UserProfile = () => {
const { user, setUser } = useContext(UserContext);
return (
<div>
<h1>User: {user}</h1>
<button onClick={() => setUser("Jane Doe")}>Change User</button>
</div>
);
};
export default function App() {
return (
<UserProvider>
<UserProfile />
</UserProvider>
);
}
Step 2: Simplifying with a Custom Hook
To avoid calling useContext(UserContext) repeatedly, let’s create a custom hook.
Custom Hook for Consuming Context
We extract the consumption logic into a custom hook:
import React, { createContext, useState, useContext } from "react";
// Create a Context
const UserContext = createContext();
// Custom Hook to Access Context
export const useUser = () => {
const context = useContext(UserContext);
if (!context) {
throw new Error("useUser must be used within a UserProvider");
}
return context;
};
// Custom Provider Component
export const UserProvider = ({ children }) => {
const [user, setUser] = useState("John Doe");
return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
};
Updated Component Using the Custom Hook
Here’s the cleaner way to consume the context:
import React from "react";
import { UserProvider, useUser } from "./UserContext";
// Consuming Context via Custom Hook
const UserProfile = () => {
const { user, setUser } = useUser();
return (
<div>
<h1>User: {user}</h1>
<button onClick={() => setUser("Jane Doe")}>Change User</button>
</div>
);
};
export default function App() {
return (
<UserProvider>
<UserProfile />
</UserProvider>
);
}
Key Improvements with Custom Hooks and Providers
- Custom Provider:
- The
UserProviderencapsulates theProvidersetup. - Simplifies wrapping components with the context.
- The
- Custom Hook:
- Encapsulates the logic for
useContext(UserContext). - Provides a clean, reusable API for consuming context.
- Encapsulates the logic for
- Error Handling:
- If
useUseris called outsideUserProvider, it throws an error. This ensures proper usage.
- If
Benefits of This Approach
- Cleaner Components: No repetitive
useContextcalls. - Reusable Code: Custom hooks can be reused across components.
- Error Safety: Guards against incorrect usage of the context.
Summary
- Use
createContextto set up shared state. - Wrap consumers with a custom provider.
- Access context using a custom hook to simplify usage.
This approach is scalable, clean, and ideal for managing global state in React applications.
Let me know if you'd like another example or an advanced use case, like combining useReducer with Context! 🚀
No hints available.