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/Passing Data Context

🎯 Complex Context Data — Managing Advanced State Structures

Managing complex, interdependent state in Context requires careful architecture. Understanding normalization, circular dependencies, and state composition prevents common pitfalls in large applications.


🎯 Nested Context Hierarchies and Interdependent State

Complex applications often require multiple levels of Context that depend on each other. Proper design prevents circular dependencies and maintains clean data flow:

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

// Define independent domain models
interface User {
  id: string;
  name: string;
  email: string;
  teamId: string;
}

interface Team {
  id: string;
  name: string;
  members: string[]; // User IDs
  ownerId: string;
}

interface Project {
  id: string;
  name: string;
  teamId: string;
  ownerId: string;
  members: string[];
}

// Separate contexts for each domain
type UserContextType = {
  users: Record<string, User>;
  currentUserId: string | null;
  setCurrentUser: (userId: string) => void;
  addUser: (user: User) => void;
};

type TeamContextType = {
  teams: Record<string, Team>;
  addTeam: (team: Team) => void;
  updateTeam: (teamId: string, team: Partial<Team>) => void;
};

type ProjectContextType = {
  projects: Record<string, Project>;
  addProject: (project: Project) => void;
  getProjectsByTeam: (teamId: string) => Project[];
};

const UserContext = createContext<UserContextType | undefined>(undefined);
const TeamContext = createContext<TeamContextType | undefined>(undefined);
const ProjectContext = createContext<ProjectContextType | undefined>(undefined);

// Composite provider managing all contexts
function DomainProvider({ children }: { children: ReactNode }) {
  // User state
  const [users, setUsers] = useState<Record<string, User>>({});
  const [currentUserId, setCurrentUserId] = useState<string | null>(null);

  const userValue: UserContextType = {
    users,
    currentUserId,
    setCurrentUser: setCurrentUserId,
    addUser: useCallback((user: User) => {
      setUsers((prev) => ({ ...prev, [user.id]: user }));
    }, []),
  };

  // Team state
  const [teams, setTeams] = useState<Record<string, Team>>({});

  const teamValue: TeamContextType = {
    teams,
    addTeam: useCallback((team: Team) => {
      setTeams((prev) => ({ ...prev, [team.id]: team }));
    }, []),
    updateTeam: useCallback((teamId: string, updates: Partial<Team>) => {
      setTeams((prev) => ({
        ...prev,
        [teamId]: { ...prev[teamId], ...updates },
      }));
    }, []),
  };

  // Project state
  const [projects, setProjects] = useState<Record<string, Project>>({});

  const projectValue: ProjectContextType = {
    projects,
    addProject: useCallback((project: Project) => {
      setProjects((prev) => ({ ...prev, [project.id]: project }));
    }, []),
    getProjectsByTeam: useCallback(
      (teamId: string) => {
        return Object.values(projects).filter((p) => p.teamId === teamId);
      },
      [projects]
    ),
  };

  return (
    <UserContext.Provider value={userValue}>
      <TeamContext.Provider value={teamValue}>
        <ProjectContext.Provider value={projectValue}>
          {children}
        </ProjectContext.Provider>
      </TeamContext.Provider>
    </UserContext.Provider>
  );
}

// Consume with cross-context queries
function TeamMembersList() {
  const userContext = useContext(UserContext)!;
  const teamContext = useContext(TeamContext)!;

  const currentTeam = Object.values(teamContext.teams)[0];
  const members = currentTeam?.members
    .map((memberId) => userContext.users[memberId])
    .filter(Boolean);

  return (
    <ul>
      {members?.map((member) => (
        <li key={member.id}>{member.name}</li>
      ))}
    </ul>
  );
}

💡 Normalized State Structure in Context

Normalizing state prevents redundancy and makes updates safer:

// Denormalized (problematic)
type BadState = {
  users: Array<{
    id: string;
    name: string;
    teams: Team[]; // Redundant team data
  }>;
};

// Normalized (better)
type GoodState = {
  users: Record<string, User>;
  teams: Record<string, Team>;
  relationships: Record<string, string[]>; // userId -> teamIds
};

interface NormalizedContextType {
  users: Record<string, User>;
  teams: Record<string, Team>;
  userTeams: Record<string, string[]>; // userId -> teamIds
  getUser: (userId: string) => User | undefined;
  getUserTeams: (userId: string) => Team[];
  addUserToTeam: (userId: string, teamId: string) => void;
  removeUserFromTeam: (userId: string, teamId: string) => void;
}

const NormalizedContext = createContext<NormalizedContextType | undefined>(
  undefined
);

function NormalizedProvider({ children }: { children: ReactNode }) {
  const [state, setState] = useState({
    users: {} as Record<string, User>,
    teams: {} as Record<string, Team>,
    userTeams: {} as Record<string, string[]>,
  });

  const value: NormalizedContextType = {
    users: state.users,
    teams: state.teams,
    userTeams: state.userTeams,

    getUser: useCallback(
      (userId: string) => state.users[userId],
      [state.users]
    ),

    getUserTeams: useCallback((userId: string) => {
      const teamIds = state.userTeams[userId] || [];
      return teamIds
        .map((teamId) => state.teams[teamId])
        .filter(Boolean);
    }, [state.teams, state.userTeams]),

    addUserToTeam: useCallback((userId: string, teamId: string) => {
      setState((prev) => ({
        ...prev,
        userTeams: {
          ...prev.userTeams,
          [userId]: Array.from(
            new Set([...(prev.userTeams[userId] || []), teamId])
          ),
        },
      }));
    }, []),

    removeUserFromTeam: useCallback((userId: string, teamId: string) => {
      setState((prev) => ({
        ...prev,
        userTeams: {
          ...prev.userTeams,
          [userId]: (prev.userTeams[userId] || []).filter(
            (id) => id !== teamId
          ),
        },
      }));
    }, []),
  };

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

// Usage is cleaner with normalized state
function UserTeams({ userId }: { userId: string }) {
  const { getUserTeams } = useContext(NormalizedContext)!;
  const teams = getUserTeams(userId);

  return (
    <ul>
      {teams.map((team) => (
        <li key={team.id}>{team.name}</li>
      ))}
    </ul>
  );
}

<details>

<summary>📚 More Examples</summary>

// Example 1: Immutable update patterns for nested data
function updateNestedContext<T>(
  state: T,
  path: string[],
  value: any
): T {
  if (path.length === 0) return value;

  const [head, ...tail] = path;
  const nested = state?.[head as keyof T] ?? {};

  return {
    ...state,
    [head]: updateNestedContext(nested, tail, value),
  };
}

// Example 2: Event-driven context updates
interface ContextEvent {
  type: string;
  payload: any;
}

function useContextEvents(
  handler: (state: any, event: ContextEvent) => any,
  initialState: any
) {
  const [state, setState] = useState(initialState);
  const listeners = new Set<() => void>();

  const dispatch = useCallback((event: ContextEvent) => {
    setState((prev) => {
      const next = handler(prev, event);
      listeners.forEach((listener) => listener());
      return next;
    });
  }, [handler]);

  return { state, dispatch, subscribe: (fn: () => void) => listeners.add(fn) };
}

// Example 3: Complex state machine in context
type OrderState = {
  status: "pending" | "processing" | "completed" | "failed";
  items: OrderItem[];
  total: number;
  error?: string;
};

type OrderAction =
  | { type: "ADD_ITEM"; payload: OrderItem }
  | { type: "REMOVE_ITEM"; payload: string }
  | { type: "START_PROCESSING" }
  | { type: "COMPLETE" }
  | { type: "FAIL"; payload: string };

function orderReducer(state: OrderState, action: OrderAction): OrderState {
  switch (action.type) {
    case "ADD_ITEM":
      return {
        ...state,
        items: [...state.items, action.payload],
        total: state.total + action.payload.price,
      };
    case "REMOVE_ITEM":
      const item = state.items.find((i) => i.id === action.payload);
      return {
        ...state,
        items: state.items.filter((i) => i.id !== action.payload),
        total: state.total - (item?.price || 0),
      };
    case "START_PROCESSING":
      return { ...state, status: "processing" };
    case "COMPLETE":
      return { ...state, status: "completed" };
    case "FAIL":
      return { ...state, status: "failed", error: action.payload };
    default:
      return state;
  }
}

const OrderContext = createContext<{
  state: OrderState;
  dispatch: (action: OrderAction) => void;
} | undefined>(undefined);

function OrderProvider({ children }: { children: ReactNode }) {
  const [state, dispatch] = useReducer(orderReducer, {
    status: "pending",
    items: [],
    total: 0,
  });

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

</details>

🎨 Real-World: E-Commerce Complex State

Managing product catalog, shopping cart, and user preferences together:

// State shape for e-commerce app
type ECommerceState = {
  catalog: {
    products: Record<string, Product>;
    categories: Record<string, Category>;
    filters: FilterState;
  };
  cart: {
    items: CartItem[];
    total: number;
    lastUpdated: number;
  };
  user: {
    preferences: UserPreferences;
    addresses: Address[];
    savedCards: PaymentMethod[];
  };
};

function ECommerceProvider({ children }: { children: ReactNode }) {
  const [state, setState] = useState<ECommerceState>({
    catalog: { products: {}, categories: {}, filters: {} },
    cart: { items: [], total: 0, lastUpdated: 0 },
    user: { preferences: {}, addresses: [], savedCards: [] },
  });

  const value = {
    state,
    // Catalog operations
    addProduct: (product: Product) => {
      setState((prev) => ({
        ...prev,
        catalog: {
          ...prev.catalog,
          products: { ...prev.catalog.products, [product.id]: product },
        },
      }));
    },
    // Cart operations
    addToCart: (product: Product, quantity: number) => {
      setState((prev) => {
        const existing = prev.cart.items.find((i) => i.productId === product.id);
        return {
          ...prev,
          cart: {
            ...prev.cart,
            items: existing
              ? prev.cart.items.map((i) =>
                  i.productId === product.id
                    ? { ...i, quantity: i.quantity + quantity }
                    : i
                )
              : [
                  ...prev.cart.items,
                  { productId: product.id, quantity, price: product.price },
                ],
            total: prev.cart.total + product.price * quantity,
          },
        };
      });
    },
  };

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

📊 Complex Data Patterns

PatternComplexityUse Case
Single contextLowSimple apps
Normalized stateMediumRelated entities
Multiple contextsHighDomain separation
State machineVery HighComplex workflows

🔑 Key Takeaways

  • ✅ Normalize state to prevent redundancy and inconsistency
  • ✅ Separate contexts by domain, not by layer
  • ✅ Use relationships objects for many-to-many connections
  • ✅ Implement derived data functions for complex queries
  • ✅ Update state immutably to prevent bugs
  • ✅ Consider state machines for complex workflows
  • ✅ Profile for circular dependencies when composing contexts
  • ✅ Use normalized selectors to optimize re-renders

Ready to practice? Challenges | Next: Context Optimization


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