
ReactJS
Advanced Context consumption involves understanding context from multiple angles: custom hooks for ergonomic access, subscriber patterns for reactive updates, and error boundaries for robust error handling.
Custom hooks provide abstraction over raw `useContext()` calls. They enable validation, selective memoization, and graceful error handling:
import { useContext, useCallback, useMemo } from "react";
interface AppContextType {
user: { id: string; name: string } | null;
setUser: (user: AppContextType["user"]) => void;
preferences: { theme: string; language: string };
updatePreferences: (prefs: Partial<AppContextType["preferences"]>) => void;
}
const AppContext = createContext<AppContextType | undefined>(undefined);
// ❌ Direct useContext requires null checks everywhere
function BadComponent() {
const context = useContext(AppContext);
if (!context) throw new Error("Not in provider");
const { user } = context;
return <div>{user?.name}</div>;
}
// ✅ Custom hook encapsulates logic
function useApp() {
const context = useContext(AppContext);
if (!context) {
throw new Error("useApp must be used within AppProvider");
}
return context;
}
function GoodComponent() {
const { user } = useApp();
return <div>{user?.name}</div>;
}
// Advanced: Selector hook for specific values
function useAppValue<T>(selector: (app: AppContextType) => T): T {
const app = useApp();
return useMemo(() => selector(app), [app, selector]);
}
// Components only re-render when selected value changes
function UserName() {
const name = useAppValue((app) => app.user?.name);
return <h1>{name}</h1>;
}
function UserEmail() {
const email = useAppValue((app) => app.user?.email);
return <p>{email}</p>;
}
// Changing theme doesn't re-render UserName or UserEmail
// because they only selected the user's name/emailBuilding custom hooks that handle multiple concerns simultaneously:
// 1. Hook with loading and error states
function useAppAsync<T,>(
fn: () => Promise<T>,
dependencies: any[]
): { data: T | null; loading: boolean; error: Error | null } {
const { fetchData } = useApp();
const [state, setState] = useState<{
data: T | null;
loading: boolean;
error: Error | null;
}>({ data: null, loading: false, error: null });
useEffect(() => {
setState({ data: null, loading: true, error: null });
fn()
.then((data) => setState({ data, loading: false, error: null }))
.catch((error) => setState({ data: null, loading: false, error }));
}, dependencies);
return state;
}
// 2. Hook for dependent state
function useAppUserData() {
const { user } = useApp();
const { data: userData } = useAppAsync(
() => (user ? fetch(`/api/users/${user.id}`).then((r) => r.json()) : Promise.resolve(null)),
[user?.id]
);
return userData;
}
// 3. Hook combining multiple context values
function useAppState() {
const app = useApp();
return useMemo(() => ({
isAuthenticated: !!app.user,
user: app.user,
theme: app.preferences.theme,
setTheme: (theme: string) =>
app.updatePreferences({ theme }),
}), [app]);
}
// 4. Hook with caching
const useAppCache = (() => {
const cache = new Map();
return function <T,>(key: string, fn: () => T): T {
if (!cache.has(key)) {
cache.set(key, fn());
}
return cache.get(key);
};
})();<details>
<summary>📚 More Examples</summary>
// Example 1: Hook with lifecycle management
function useAppListener(
callback: (app: AppContextType) => void,
dependencies: any[] = []
) {
const app = useApp();
useEffect(() => {
callback(app);
}, [app, ...dependencies]);
}
// Example 2: Hook for mutations
function useAppMutations() {
const app = useApp();
return useMemo(() => ({
updateUser: (user: Partial<AppContextType["user"]>) => {
app.setUser({ ...app.user, ...user });
},
updateTheme: (theme: string) => {
app.updatePreferences({ theme });
},
}), [app]);
}
// Example 3: Hook with derived state
function useAppDerivedState() {
const { user, preferences } = useApp();
return useMemo(() => ({
isDarkMode: preferences.theme === "dark",
isAdmin: user?.role === "admin",
displayName: user?.name.toUpperCase(),
}), [user, preferences]);
}
// Example 4: Hook with batch updates
function useAppBatchUpdate() {
const app = useApp();
return useCallback((updates: Partial<AppContextType>) => {
Object.entries(updates).forEach(([key, value]) => {
if (key === "user") app.setUser(value as any);
if (key === "preferences") app.updatePreferences(value as any);
});
}, [app]);
}</details>
Context works best when paired with error boundaries for resilient applications:
interface ErrorBoundaryProps {
children: ReactNode;
fallback?: (error: Error, reset: () => void) => ReactNode;
}
interface ErrorBoundaryState {
hasError: boolean;
error: Error | null;
}
class ContextErrorBoundary extends React.Component<
ErrorBoundaryProps,
ErrorBoundaryState
> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { hasError: true, error };
}
render() {
if (this.state.hasError && this.state.error) {
return (
this.props.fallback?.(
this.state.error,
() => this.setState({ hasError: false, error: null })
) || (
<div>
<h1>Something went wrong</h1>
<p>{this.state.error.message}</p>
</div>
)
);
}
return this.props.children;
}
}
// Usage with context
function App() {
return (
<ContextErrorBoundary
fallback={(error, reset) => (
<div>
<h2>Context Error</h2>
<p>{error.message}</p>
<button onClick={reset}>Try again</button>
</div>
)}
>
<AppProvider>
<MainApp />
</AppProvider>
</ContextErrorBoundary>
);
}
// Advanced: Context-aware error boundary
function useErrorHandler() {
const { reportError } = useApp();
return useCallback((error: Error) => {
reportError(error);
throw error;
}, []);
}
function SafeComponent() {
const handleError = useErrorHandler();
return (
<ContextErrorBoundary
fallback={(error) => <div>Error: {error.message}</div>}
>
<RiskyComponent onError={handleError} />
</ContextErrorBoundary>
);
}| Pattern | Use Case | Benefit |
|---|---|---|
| Direct useContext | Simple access | Minimal boilerplate |
| Custom hook | Validation | Clear errors |
| Selector hook | Performance | Prevents re-renders |
| Error boundary | Safety | Graceful failures |
| Listener hook | Reactions | Side effects |
Ready to practice? Challenges | Next: Complex Context Data
Resources
Ojasa Mirai
Master AI-powered development skills through structured learning, real projects, and verified credentials. Whether you're upskilling your team or launching your career, we deliver the skills companies actually need.
Learn Deep • Build Real • Verify Skills • Launch Forward