
ReactJS
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.
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>
);
}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>
// ✅ 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";
}| Requirement | Context | Zustand | Redux |
|---|---|---|---|
| Simple state | ✅ Great | Good | Overkill |
| Medium state | Good | ✅ Best | Good |
| Complex state | Fair | Good | ✅ Best |
| Performance | Good | ✅ Great | Great |
| Learning curve | Low | ✅ Low | High |
| DevTools | Limited | ✅ Good | ✅ Great |
Ready to practice? Challenges | Quiz
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