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 Best Practices

🏗️ Context Architecture at Scale — Enterprise Patterns and Production Systems

Building scalable Context architectures for large applications requires architectural patterns, comprehensive testing strategies, and clear separation of concerns. Understanding when Context is appropriate and when alternative solutions are better is crucial.


🎯 Enterprise Context Architecture

Large applications benefit from a layered, feature-based Context architecture:

// src/contexts/index.ts - Central export point
export * from "./auth/AuthContext";
export * from "./ui/UIContext";
export * from "./data/DataContext";

// src/contexts/auth/AuthContext.tsx
import {
  createContext,
  useContext,
  useState,
  useCallback,
  ReactNode,
} from "react";

interface User {
  id: string;
  email: string;
  role: "admin" | "user";
}

interface AuthContextType {
  user: User | null;
  isLoading: boolean;
  error: Error | null;
  login(email: string, password: string): Promise<void>;
  logout(): void;
  register(email: string, password: string): Promise<void>;
}

const AuthContext = createContext<AuthContextType | undefined>(undefined);

export function AuthProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<User | null>(null);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<Error | null>(null);

  const login = useCallback(async (email: string, password: string) => {
    setIsLoading(true);
    setError(null);
    try {
      const response = await fetch("/api/auth/login", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ email, password }),
      });

      if (!response.ok) throw new Error("Login failed");

      const user = await response.json();
      setUser(user);
    } catch (err) {
      setError(err as Error);
      throw err;
    } finally {
      setIsLoading(false);
    }
  }, []);

  const logout = useCallback(() => {
    setUser(null);
    setError(null);
  }, []);

  const register = useCallback(async (email: string, password: string) => {
    setIsLoading(true);
    setError(null);
    try {
      const response = await fetch("/api/auth/register", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ email, password }),
      });

      if (!response.ok) throw new Error("Registration failed");

      const user = await response.json();
      setUser(user);
    } catch (err) {
      setError(err as Error);
      throw err;
    } finally {
      setIsLoading(false);
    }
  }, []);

  const value: AuthContextType = {
    user,
    isLoading,
    error,
    login,
    logout,
    register,
  };

  return (
    <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
  );
}

export function useAuth() {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error("useAuth must be used within AuthProvider");
  }
  return context;
}

// Selector hooks for specific slices
export function useAuthUser() {
  const { user } = useAuth();
  return user;
}

export function useAuthError() {
  const { error, isLoading } = useAuth();
  return { error, isLoading };
}

// src/App.tsx - Root provider composition
import { AuthProvider } from "./contexts/auth/AuthContext";
import { UIProvider } from "./contexts/ui/UIContext";
import { DataProvider } from "./contexts/data/DataContext";

export default function App() {
  return (
    <AuthProvider>
      <UIProvider>
        <DataProvider>
          <MainApp />
        </DataProvider>
      </UIProvider>
    </AuthProvider>
  );
}

💡 Testing Context Thoroughly

Comprehensive testing ensures Context behaves correctly across all scenarios:

// src/contexts/auth/__tests__/AuthContext.test.tsx
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { AuthProvider, useAuth } from "../AuthContext";
import { ReactNode } from "react";

// Wrapper component for testing
function TestWrapper({ children }: { children: ReactNode }) {
  return <AuthProvider>{children}</AuthProvider>;
}

describe("AuthContext", () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe("Login", () => {
    it("should login successfully", async () => {
      const TestComponent = () => {
        const { user, login, isLoading } = useAuth();

        return (
          <div>
            <button onClick={() => login("user@example.com", "password")}>
              Login
            </button>
            {isLoading && <p>Loading...</p>}
            {user && <p>Welcome, {user.email}</p>}
          </div>
        );
      };

      render(<TestComponent />, { wrapper: TestWrapper });

      const loginButton = screen.getByRole("button", { name: /login/i });
      await userEvent.click(loginButton);

      expect(screen.getByText(/loading/i)).toBeInTheDocument();

      await waitFor(() => {
        expect(screen.getByText(/welcome/i)).toBeInTheDocument();
      });
    });

    it("should handle login errors", async () => {
      global.fetch = jest.fn().mockRejectedValue(new Error("Network error"));

      const TestComponent = () => {
        const { error, login } = useAuth();

        return (
          <div>
            <button onClick={() => login("user@example.com", "password")}>
              Login
            </button>
            {error && <p>Error: {error.message}</p>}
          </div>
        );
      };

      render(<TestComponent />, { wrapper: TestWrapper });

      await userEvent.click(screen.getByRole("button", { name: /login/i }));

      await waitFor(() => {
        expect(screen.getByText(/error/i)).toBeInTheDocument();
      });
    });
  });

  describe("Logout", () => {
    it("should logout successfully", async () => {
      const TestComponent = () => {
        const { user, login, logout } = useAuth();

        return (
          <div>
            <button onClick={() => login("user@example.com", "password")}>
              Login
            </button>
            <button onClick={logout}>Logout</button>
            {user ? <p>User logged in</p> : <p>User logged out</p>}
          </div>
        );
      };

      render(<TestComponent />, { wrapper: TestWrapper });

      await userEvent.click(screen.getByRole("button", { name: /login/i }));

      await waitFor(() => {
        expect(screen.getByText(/user logged in/i)).toBeInTheDocument();
      });

      await userEvent.click(screen.getByRole("button", { name: /logout/i }));

      expect(screen.getByText(/user logged out/i)).toBeInTheDocument();
    });
  });

  describe("useAuth hook", () => {
    it("should throw error when used outside provider", () => {
      const TestComponent = () => {
        useAuth(); // Should throw
        return null;
      };

      // Suppress console.error for this test
      const spy = jest.spyOn(console, "error").mockImplementation();

      expect(() => render(<TestComponent />)).toThrow(
        "useAuth must be used within AuthProvider"
      );

      spy.mockRestore();
    });
  });
});

// Integration tests
describe("AuthContext Integration", () => {
  it("should maintain user state across component tree", async () => {
    const Header = () => {
      const { user } = useAuth();
      return user ? <h1>Welcome, {user.email}</h1> : <h1>Guest</h1>;
    };

    const LoginForm = () => {
      const { login } = useAuth();
      return (
        <button onClick={() => login("user@example.com", "password")}>
          Login
        </button>
      );
    };

    const { rerender } = render(
      <AuthProvider>
        <Header />
        <LoginForm />
      </AuthProvider>
    );

    expect(screen.getByText(/guest/i)).toBeInTheDocument();

    await userEvent.click(screen.getByRole("button", { name: /login/i }));

    await waitFor(() => {
      expect(screen.getByText(/welcome/i)).toBeInTheDocument();
    });
  });
});

<details>

<summary>📚 More Examples</summary>

// Example 1: Mock provider for testing
export function createMockAuthProvider(initialUser: User | null = null) {
  return function MockAuthProvider({ children }: { children: ReactNode }) {
    return (
      <AuthContext.Provider
        value={{
          user: initialUser,
          isLoading: false,
          error: null,
          login: jest.fn().mockResolvedValue(undefined),
          logout: jest.fn(),
          register: jest.fn().mockResolvedValue(undefined),
        }}
      >
        {children}
      </AuthContext.Provider>
    );
  };
}

// Usage in tests
describe("UserProfile", () => {
  it("should display user information", () => {
    const mockUser = { id: "1", email: "test@example.com", role: "user" };
    const MockAuthProvider = createMockAuthProvider(mockUser);

    render(
      <MockAuthProvider>
        <UserProfile />
      </MockAuthProvider>
    );

    expect(screen.getByText(mockUser.email)).toBeInTheDocument();
  });
});

// Example 2: Testing context selectors
describe("Auth Selectors", () => {
  it("useAuthUser should return user only", () => {
    const TestComponent = () => {
      const user = useAuthUser();
      return <div>{user?.email}</div>;
    };

    const { rerender } = render(
      <AuthProvider>
        <TestComponent />
      </AuthProvider>
    );

    // Verify only user is selected, not entire auth state
  });
});

// Example 3: Context integration with MSW (Mock Service Worker)
import { setupServer } from "msw/node";
import { rest } from "msw";

const server = setupServer(
  rest.post("/api/auth/login", (req, res, ctx) => {
    return res(ctx.json({ id: "1", email: "user@example.com", role: "user" }));
  })
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

describe("AuthContext with MSW", () => {
  it("should login with mocked API", async () => {
    // Test with realistic API mocking
  });
});

</details>

🎨 Real-World: When to Use Context vs Alternatives

// ✅ USE CONTEXT FOR:
// - Authentication state (user info, permissions)
// - Theme/language (app-wide, rarely changes)
// - Global UI state (modals, notifications)
// - Feature flags
// - Read-only configuration

// ❌ AVOID CONTEXT FOR:
// - Form state (use local useState instead)
// - Real-time data updates (use WebSocket + external state)
// - Frequently changing data (use Zustand, Jotai, Redux)
// - Server state (use React Query, SWR)

// Alternative solutions:
// - Zustand: Lightweight, good for general state
// - Jotai: Atomic state management
// - Redux: Complex apps with complex state
// - React Query: Server state management
// - Signals: Reactive state (third-party libraries)

function decideStateManagement(requirement: {
  complexity: "simple" | "medium" | "complex";
  frequency: "rarely" | "occasionally" | "frequently";
  scope: "local" | "app-wide";
  type: "client" | "server";
}): string {
  if (requirement.type === "server") return "React Query / SWR";
  if (requirement.scope === "local") return "useState / useReducer";
  if (requirement.frequency === "frequently") return "Zustand / Jotai / Redux";
  if (requirement.complexity === "complex") return "Redux / Zustand";

  return "Context API";
}

📊 Architecture Decision Matrix

RequirementContextZustandRedux
Simple state✅ GreatGoodOverkill
Medium stateGood✅ BestGood
Complex stateFairGood✅ Best
PerformanceGood✅ GreatGreat
Learning curveLow✅ LowHigh
DevToolsLimited✅ Good✅ Great

🔑 Key Takeaways

  • ✅ Organize Context by feature domains, not layers
  • ✅ Export Provider and custom hooks from context modules
  • ✅ Test Context thoroughly with multiple scenarios
  • ✅ Use mock providers to simplify testing
  • ✅ Create selector hooks to optimize re-renders
  • ✅ Consider alternative state solutions for specific use cases
  • ✅ Document when to use Context vs other solutions
  • ✅ Profile and measure performance in production
  • ✅ Keep Context focused on app-wide, rarely-changing data
  • ✅ Avoid Context for frequently-changing or complex state

Ready to practice? Challenges | Quiz


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