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/Creating Context

📦 Advanced Context Design — Building Type-Safe Context Systems

Advanced Context design involves creating reusable, type-safe context factories using TypeScript generics. This allows you to build a consistent context pattern across your application while maintaining strict type safety.


🎯 Context Factories with TypeScript Generics

A context factory is a function that generates a Context, Provider, and hook with full type safety. This pattern eliminates boilerplate and ensures consistency:

import { createContext, useContext, useState, ReactNode } from "react";

// Generic factory function
function createContextModule<T>(defaultValue: T) {
  const Context = createContext<T | undefined>(undefined);

  function Provider({ children, value }: { children: ReactNode; value: T }) {
    return <Context.Provider value={value}>{children}</Context.Provider>;
  }

  function useContextModule() {
    const context = useContext(Context);
    if (context === undefined) {
      throw new Error("useContextModule must be used within Provider");
    }
    return context;
  }

  return { Provider, useContextModule, Context };
}

// Example: Create theme context with factory
interface ThemeConfig {
  primaryColor: string;
  secondaryColor: string;
  fontSize: number;
  isDark: boolean;
}

const ThemeContextModule = createContextModule<ThemeConfig>({
  primaryColor: "#007bff",
  secondaryColor: "#6c757d",
  fontSize: 16,
  isDark: false,
});

function App() {
  return (
    <ThemeContextModule.Provider
      value={{
        primaryColor: "#007bff",
        secondaryColor: "#6c757d",
        fontSize: 16,
        isDark: false,
      }}
    >
      <Header />
    </ThemeContextModule.Provider>
  );
}

function Header() {
  const theme = ThemeContextModule.useContextModule();
  return <h1 style={{ color: theme.primaryColor }}>Header</h1>;
}

💡 Advanced Multi-Context Setup with Composition

For large applications, you often need multiple Contexts that work together. The composition pattern allows you to create a unified state management layer:

import {
  createContext,
  useContext,
  useState,
  useCallback,
  ReactNode,
} from "react";

// Define separate state shapes
interface UserState {
  id: string;
  name: string;
  email: string;
}

interface UIState {
  sidebarOpen: boolean;
  themeMode: "light" | "dark";
  selectedTab: string;
}

interface AppContextType {
  user: UserState;
  ui: UIState;
  setUser: (user: UserState) => void;
  updateUI: (partial: Partial<UIState>) => void;
}

const AppContext = createContext<AppContextType | undefined>(undefined);

// Unified provider managing multiple state slices
export function AppProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<UserState>({
    id: "1",
    name: "Alice",
    email: "alice@example.com",
  });

  const [ui, setUI] = useState<UIState>({
    sidebarOpen: true,
    themeMode: "light",
    selectedTab: "home",
  });

  const updateUI = useCallback((partial: Partial<UIState>) => {
    setUI((prev) => ({ ...prev, ...partial }));
  }, []);

  const value: AppContextType = {
    user,
    ui,
    setUser,
    updateUI,
  };

  return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
}

// Single hook to access the entire context
export function useAppContext() {
  const context = useContext(AppContext);
  if (!context) {
    throw new Error("useAppContext must be within AppProvider");
  }
  return context;
}

// Selector hooks for specific slices (prevents unnecessary re-renders)
export function useUser() {
  const { user } = useAppContext();
  return user;
}

export function useUI() {
  const { ui } = useAppContext();
  return ui;
}

export function useThemeMode() {
  const { ui, updateUI } = useAppContext();
  return {
    themeMode: ui.themeMode,
    setThemeMode: (mode: "light" | "dark") => updateUI({ themeMode: mode }),
  };
}

<details>

<summary>📚 More Examples</summary>

// Example 1: Generic state context factory with actions
interface Action<T> {
  type: string;
  payload?: any;
}

function createStateContext<T, A extends Action<T>>(
  reducer: (state: T, action: A) => T,
  initialState: T
) {
  const Context = createContext<{ state: T; dispatch: (action: A) => void } | undefined>(
    undefined
  );

  function Provider({ children }: { children: ReactNode }) {
    const [state, dispatch] = useState(initialState);

    const handleDispatch = (action: A) => {
      dispatch((prev) => reducer(prev, action));
    };

    return (
      <Context.Provider value={{ state, dispatch: handleDispatch }}>
        {children}
      </Context.Provider>
    );
  }

  function useContextState() {
    const context = useContext(Context);
    if (!context) {
      throw new Error("useContextState must be within Provider");
    }
    return context;
  }

  return { Provider, useContextState };
}

// Example usage
type CounterState = { count: number; step: number };
type CounterAction =
  | { type: "INCREMENT"; payload?: void }
  | { type: "DECREMENT"; payload?: void }
  | { type: "SET_STEP"; payload: number };

const counterReducer = (state: CounterState, action: CounterAction) => {
  switch (action.type) {
    case "INCREMENT":
      return { ...state, count: state.count + state.step };
    case "DECREMENT":
      return { ...state, count: state.count - state.step };
    case "SET_STEP":
      return { ...state, step: action.payload };
    default:
      return state;
  }
};

const CounterContext = createStateContext(counterReducer, {
  count: 0,
  step: 1,
});

function Counter() {
  const { state, dispatch } = CounterContext.useContextState();
  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: "INCREMENT" })}>+</button>
      <input
        type="number"
        value={state.step}
        onChange={(e) =>
          dispatch({ type: "SET_STEP", payload: Number(e.target.value) })
        }
      />
    </div>
  );
}

// Example 2: Async context operations
interface AsyncState<T> {
  data: T | null;
  loading: boolean;
  error: Error | null;
}

function createAsyncContext<T>(defaultValue: T) {
  const Context = createContext<
    | {
        state: AsyncState<T>;
        fetchData: (fn: () => Promise<T>) => Promise<void>;
        reset: () => void;
      }
    | undefined
  >(undefined);

  function Provider({ children }: { children: ReactNode }) {
    const [state, setState] = useState<AsyncState<T>>({
      data: defaultValue,
      loading: false,
      error: null,
    });

    const fetchData = async (fn: () => Promise<T>) => {
      setState({ data: null, loading: true, error: null });
      try {
        const data = await fn();
        setState({ data, loading: false, error: null });
      } catch (error) {
        setState({ data: null, loading: false, error: error as Error });
      }
    };

    const reset = () => {
      setState({ data: defaultValue, loading: false, error: null });
    };

    return (
      <Context.Provider value={{ state, fetchData, reset }}>
        {children}
      </Context.Provider>
    );
  }

  function useAsyncContext() {
    const context = useContext(Context);
    if (!context) {
      throw new Error("useAsyncContext must be within Provider");
    }
    return context;
  }

  return { Provider, useAsyncContext };
}

// Usage
const UserDataContext = createAsyncContext<UserState>({
  id: "",
  name: "",
  email: "",
});

function UserProfile() {
  const { state, fetchData } = UserDataContext.useAsyncContext();

  if (state.loading) return <p>Loading...</p>;
  if (state.error) return <p>Error: {state.error.message}</p>;
  if (!state.data) return <p>No data</p>;

  return <div>{state.data.name}</div>;
}

</details>

🎨 Real-World: Type-Safe Multi-Feature Context Architecture

A production-ready context system organizing multiple features:

// types/context.ts
export interface ContextModule<T> {
  Provider: (props: { children: ReactNode; value?: T }) => JSX.Element;
  useModule: () => T;
  Context: React.Context<T | undefined>;
}

// contexts/createContextModule.ts
import { createContext, useContext, ReactNode } from "react";

export function createContextModule<T>(name: string): ContextModule<T> {
  const Context = createContext<T | undefined>(undefined);

  function Provider({ children, value }: { children: ReactNode; value?: T }) {
    if (!value) {
      throw new Error(`${name} Provider requires a value prop`);
    }
    return <Context.Provider value={value}>{children}</Context.Provider>;
  }

  function useModule(): T {
    const context = useContext(Context);
    if (!context) {
      throw new Error(`${name} hook used outside of its Provider`);
    }
    return context;
  }

  return { Provider, useModule, Context };
}

// contexts/auth/types.ts
export interface AuthContextValue {
  user: { id: string; email: string } | null;
  login(email: string, password: string): Promise<void>;
  logout(): void;
  isLoading: boolean;
}

// contexts/auth/AuthContext.tsx
import { createContextModule } from "../createContextModule";
import { AuthContextValue } from "./types";

export const AuthContext = createContextModule<AuthContextValue>("Auth");

// hooks/useAuth.ts
export function useAuth() {
  return AuthContext.useModule();
}

// contexts/ui/types.ts
export interface UIContextValue {
  isDarkMode: boolean;
  toggleDarkMode: () => void;
  sidebarOpen: boolean;
  toggleSidebar: () => void;
}

// contexts/ui/UIContext.tsx
import { createContextModule } from "../createContextModule";
import { UIContextValue } from "./types";

export const UIContext = createContextModule<UIContextValue>("UI");

// hooks/useUI.ts
export function useUI() {
  return UIContext.useModule();
}

// Root App with all contexts composed
function App() {
  const [authState, setAuthState] = useState<AuthContextValue>({
    user: null,
    login: async (email, password) => {
      // Login logic
    },
    logout: () => {},
    isLoading: false,
  });

  const [uiState, setUIState] = useState<UIContextValue>({
    isDarkMode: false,
    toggleDarkMode: () =>
      setUIState((prev) => ({ ...prev, isDarkMode: !prev.isDarkMode })),
    sidebarOpen: true,
    toggleSidebar: () =>
      setUIState((prev) => ({ ...prev, sidebarOpen: !prev.sidebarOpen })),
  });

  return (
    <AuthContext.Provider value={authState}>
      <UIContext.Provider value={uiState}>
        <MainApp />
      </UIContext.Provider>
    </AuthContext.Provider>
  );
}

// Using in components
function Header() {
  const { user } = useAuth();
  const { isDarkMode, toggleDarkMode } = useUI();

  return (
    <header style={{ background: isDarkMode ? "#000" : "#fff" }}>
      <h1>{user?.email}</h1>
      <button onClick={toggleDarkMode}>Toggle Theme</button>
    </header>
  );
}

📊 Design Patterns Comparison

PatternComplexityFlexibilityType Safety
Simple ContextLowMediumGood
Factory PatternMediumHighExcellent
Multi-featureHighVery HighExcellent
Redux-likeVery HighMaximumExcellent

🔑 Key Takeaways

  • ✅ Use context factories for consistent, reusable patterns
  • ✅ Leverage TypeScript generics for type-safe context creation
  • ✅ Organize contexts by feature, not by data type
  • ✅ Compose multiple contexts for complex applications
  • ✅ Create selector hooks to optimize re-renders
  • ✅ Validate context usage with error-throwing hooks
  • ✅ Use discriminated unions for action types
  • ✅ Consider context factories before building custom solutions

Ready to practice? Challenges | Next: Multiple Providers


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