
ReactJS
Advanced performance optimization with Context involves context splitting strategies, selective subscriptions, profiling techniques, and memoization patterns that prevent re-render cascades in large applications.
The primary performance issue with Context is that all subscribers re-render when any part of the context value changes. Splitting related state into separate contexts allows components to opt-in to specific updates:
import { createContext, useContext, useState, ReactNode, useMemo } from "react";
// Problem: Single context causes re-renders
// When either theme OR user changes, ALL subscribers re-render
interface AppContextType {
user: User | null;
setUser: (user: User | null) => void;
theme: "light" | "dark";
setTheme: (theme: "light" | "dark") => void;
}
const AppContext = createContext<AppContextType | undefined>(undefined);
// ❌ This causes unnecessary re-renders
function ProblematicApp() {
const [user, setUser] = useState<User | null>(null);
const [theme, setTheme] = useState<"light" | "dark">("light");
const value = { user, setUser, theme, setTheme };
return (
<AppContext.Provider value={value}>
<UserComponent /> {/* Re-renders on theme change */}
<ThemeComponent /> {/* Re-renders on user change */}
</AppContext.Provider>
);
}
// Solution: Split into separate contexts
interface UserContextType {
user: User | null;
setUser: (user: User | null) => void;
}
interface ThemeContextType {
theme: "light" | "dark";
setTheme: (theme: "light" | "dark") => void;
}
const UserContext = createContext<UserContextType | undefined>(undefined);
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
// ✅ Separate contexts prevent unnecessary re-renders
function OptimizedApp() {
const [user, setUser] = useState<User | null>(null);
const [theme, setTheme] = useState<"light" | "dark">("light");
// Use useMemo to prevent new object every render
const userValue = useMemo(() => ({ user, setUser }), [user]);
const themeValue = useMemo(() => ({ theme, setTheme }), [theme]);
return (
<UserContext.Provider value={userValue}>
<ThemeContext.Provider value={themeValue}>
<UserComponent /> {/* Re-renders ONLY on user change */}
<ThemeComponent /> {/* Re-renders ONLY on theme change */}
</ThemeContext.Provider>
</UserContext.Provider>
);
}
function UserComponent() {
const { user } = useContext(UserContext)!;
// Only re-renders when user changes
return <div>User: {user?.name}</div>;
}
function ThemeComponent() {
const { theme } = useContext(ThemeContext)!;
// Only re-renders when theme changes
return <div>Theme: {theme}</div>;
}Implement a subscription pattern where components only re-render when their specific selected values change:
import { useEffect, useState, useRef, useCallback } from "react";
// Subscription-based context
type Listener<T> = (value: T) => void;
class ContextStore<T> {
private value: T;
private listeners = new Map<Listener<any>, Listener<any>>();
constructor(initialValue: T) {
this.value = initialValue;
}
getValue(): T {
return this.value;
}
setValue(value: T): void {
if (value !== this.value) {
this.value = value;
this.notify();
}
}
subscribe<S>(selector: (value: T) => S, listener: Listener<S>): () => void {
const wrappedListener = (value: T) => {
listener(selector(value));
};
this.listeners.set(listener, wrappedListener);
return () => {
this.listeners.delete(listener);
};
}
private notify(): void {
this.listeners.forEach((listener) => listener(this.value));
}
}
// Usage with custom hook
function useContextValue<T, S>(
store: ContextStore<T>,
selector: (value: T) => S
): S {
const [selected, setSelected] = useState(() => selector(store.getValue()));
useEffect(() => {
return store.subscribe(selector, setSelected);
}, [store, selector]);
return selected;
}
// Example
interface AppState {
user: User | null;
notifications: Notification[];
theme: string;
sidebarOpen: boolean;
}
const appStore = new ContextStore<AppState>({
user: null,
notifications: [],
theme: "light",
sidebarOpen: true,
});
// Only re-render when user changes
function UserProfile() {
const user = useContextValue(appStore, (state) => state.user);
return <div>{user?.name}</div>;
}
// Only re-render when notification count changes
function NotificationBadge() {
const count = useContextValue(
appStore,
(state) => state.notifications.length
);
return <span>{count}</span>;
}
// Changing theme doesn't affect these components at all<details>
<summary>📚 More Examples</summary>
// Example 1: Memoization strategies
const MemoizedUserComponent = memo(
({ user }: { user: User | null }) => <div>{user?.name}</div>,
(prev, next) => prev.user?.id === next.user?.id
);
// Example 2: useCallback dependency tracking
function useAppActions() {
const store = useAppStore();
return useMemo(() => ({
updateUser: useCallback(
(user: User) => store.updateUser(user),
[store]
),
updateTheme: useCallback(
(theme: string) => store.updateTheme(theme),
[store]
),
}), [store]);
}
// Example 3: Batching updates
function useBatchUpdates() {
const store = useAppStore();
return useCallback((updates: Partial<AppState>) => {
// Update multiple state slices in one batch
// Listeners notified once after all updates
Object.entries(updates).forEach(([key, value]) => {
store.updateField(key as keyof AppState, value);
});
}, [store]);
}
// Example 4: Immutable update helpers
function useImutableUpdates() {
const store = useAppStore();
return {
addNotification: useCallback((notification: Notification) => {
const state = store.getValue();
store.setValue({
...state,
notifications: [...state.notifications, notification],
});
}, [store]),
clearNotifications: useCallback(() => {
const state = store.getValue();
store.setValue({ ...state, notifications: [] });
}, [store]),
};
}</details>
Identifying and fixing performance bottlenecks:
// React DevTools Profiler integration
function ProfiledContextProvider({
children,
}: {
children: ReactNode;
}) {
const [state, setState] = useState(initialState);
// Track render times
const handleStateChange = useCallback((newState: AppState) => {
const startTime = performance.now();
setState(newState);
const endTime = performance.now();
console.log(`State update took ${endTime - startTime}ms`);
}, []);
return (
<Profiler id="AppContext" onRender={(id, phase, actualDuration) => {
console.log(`${id} (${phase}) took ${actualDuration}ms`);
}}>
<AppContext.Provider value={{ state, setState: handleStateChange }}>
{children}
</AppContext.Provider>
</Profiler>
);
}
// Performance monitoring hook
function useContextPerformance(contextName: string) {
const renderStartTime = useRef(performance.now());
useEffect(() => {
const renderTime = performance.now() - renderStartTime.current;
if (renderTime > 16.67) {
// Longer than 60fps
console.warn(
`${contextName} component took ${renderTime}ms to render`
);
}
});
}
// Usage
function OptimizedComponent() {
useContextPerformance("OptimizedComponent");
const data = useContextValue(store, (state) => state.data);
return <div>{data}</div>;
}| Pattern | Benefit | Cost |
|---|---|---|
| Context splitting | Fewer re-renders | More contexts |
| Selectors | Fine-grained updates | Hook complexity |
| Memoization | Prevent re-creation | Memory overhead |
| Batching | Fewer notifications | Batch complexity |
| Subscription | Optimal performance | Custom infrastructure |
Ready to practice? Challenges | Next: Context Architecture
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