Ojasa Mirai

Ojasa Mirai

ReactJS

Loading...

Learning Level

🟢 Beginner🔵 Advanced
🌍 Context API Basics📦 Creating Context🔌 Context.Provider🔍 useContext Hook🎯 Passing Data with Context🔄 Context with useState⚡ Context Performance🏗️ Context Best Practices
Reactjs/Context/Context Performance

⚡ Performance Engineering with Context — Scaling to Large Applications

Advanced performance optimization with Context involves context splitting strategies, selective subscriptions, profiling techniques, and memoization patterns that prevent re-render cascades in large applications.


🎯 Context Splitting for Optimal Performance

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>;
}

💡 Selective Subscriptions with Custom Hooks

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>

🎨 Real-World: Profiling and Optimization

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>;
}

📊 Performance Patterns Summary

PatternBenefitCost
Context splittingFewer re-rendersMore contexts
SelectorsFine-grained updatesHook complexity
MemoizationPrevent re-creationMemory overhead
BatchingFewer notificationsBatch complexity
SubscriptionOptimal performanceCustom infrastructure

🔑 Key Takeaways

  • ✅ Split related state into separate contexts when change rates differ
  • ✅ Use selector hooks to subscribe to specific state slices
  • ✅ Memoize context values with `useMemo` to prevent new objects
  • ✅ Implement subscription patterns for fine-grained reactivity
  • ✅ Profile with React DevTools to identify bottlenecks
  • ✅ Batch updates when possible to reduce notifications
  • ✅ Use immutable update patterns to prevent bugs
  • ✅ Consider alternative solutions (Zustand, Jotai) for performance-critical apps

Ready to practice? Challenges | Next: Context Architecture


Resources

Python Docs

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

Courses

PythonFastapiReactJSCloud

© 2026 Ojasa Mirai. All rights reserved.

TwitterGitHubLinkedIn