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 Usestate

🔄 Context Optimization Patterns — Advanced State Management

Advanced state management with Context involves using `useReducer` for complex state transitions, implementing Redux-like patterns, and adding middleware for side effects. These patterns create scalable, maintainable state solutions.


🎯 useReducer with Context for Complex State

`useReducer` is preferable to `useState` when state logic is complex or involves multiple related state updates:

import { useReducer, createContext, useContext, ReactNode, Dispatch } from "react";

// Define state shape
interface AppState {
  user: { id: string; name: string } | null;
  isLoading: boolean;
  error: Error | null;
  notifications: Array<{ id: string; message: string }>;
}

// Define action types
type AppAction =
  | { type: "LOGIN_START" }
  | { type: "LOGIN_SUCCESS"; payload: { id: string; name: string } }
  | { type: "LOGIN_ERROR"; payload: Error }
  | { type: "LOGOUT" }
  | { type: "ADD_NOTIFICATION"; payload: { message: string } }
  | { type: "REMOVE_NOTIFICATION"; payload: string }
  | { type: "CLEAR_ERROR" };

// Reducer function
function appReducer(state: AppState, action: AppAction): AppState {
  switch (action.type) {
    case "LOGIN_START":
      return { ...state, isLoading: true, error: null };
    case "LOGIN_SUCCESS":
      return {
        ...state,
        user: action.payload,
        isLoading: false,
        error: null,
      };
    case "LOGIN_ERROR":
      return { ...state, isLoading: false, error: action.payload };
    case "LOGOUT":
      return { ...state, user: null };
    case "ADD_NOTIFICATION":
      return {
        ...state,
        notifications: [
          ...state.notifications,
          { id: Date.now().toString(), message: action.payload.message },
        ],
      };
    case "REMOVE_NOTIFICATION":
      return {
        ...state,
        notifications: state.notifications.filter(
          (n) => n.id !== action.payload
        ),
      };
    case "CLEAR_ERROR":
      return { ...state, error: null };
    default:
      return state;
  }
}

// Context with state and dispatch
const AppContext = createContext<
  | {
      state: AppState;
      dispatch: Dispatch<AppAction>;
    }
  | undefined
>(undefined);

function AppProvider({ children }: { children: ReactNode }) {
  const [state, dispatch] = useReducer(appReducer, {
    user: null,
    isLoading: false,
    error: null,
    notifications: [],
  });

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

// Consumer hook
function useAppState() {
  const context = useContext(AppContext);
  if (!context) {
    throw new Error("useAppState must be within AppProvider");
  }
  return context;
}

// Component usage
function LoginForm() {
  const { state, dispatch } = useAppState();

  const handleLogin = async (email: string, password: string) => {
    dispatch({ type: "LOGIN_START" });
    try {
      const user = await api.login(email, password);
      dispatch({ type: "LOGIN_SUCCESS", payload: user });
      dispatch({
        type: "ADD_NOTIFICATION",
        payload: { message: "Login successful!" },
      });
    } catch (error) {
      dispatch({ type: "LOGIN_ERROR", payload: error as Error });
    }
  };

  return (
    <form onSubmit={(e) => handleLogin("user@example.com", "password")}>
      {state.isLoading && <p>Loading...</p>}
      {state.error && <p>Error: {state.error.message}</p>}
      <button type="submit">Login</button>
    </form>
  );
}

💡 Redux-Like Pattern with Context

Implement a Redux-like pattern using Context for more predictable state management:

// Redux-style store
interface Store<S, A> {
  getState: () => S;
  dispatch: (action: A) => void;
  subscribe: (listener: () => void) => () => void;
}

function createStore<S, A>(
  reducer: (state: S, action: A) => S,
  initialState: S
): Store<S, A> {
  let state = initialState;
  const listeners = new Set<() => void>();

  return {
    getState: () => state,
    dispatch: (action: A) => {
      const prevState = state;
      state = reducer(state, action);
      if (state !== prevState) {
        listeners.forEach((listener) => listener());
      }
    },
    subscribe: (listener: () => void) => {
      listeners.add(listener);
      return () => listeners.delete(listener);
    },
  };
}

// Redux store in Context
const StoreContext = createContext<Store<AppState, AppAction> | undefined>(
  undefined
);

function StoreProvider({
  children,
  store,
}: {
  children: ReactNode;
  store: Store<AppState, AppAction>;
}) {
  return (
    <StoreContext.Provider value={store}>{children}</StoreContext.Provider>
  );
}

function useStore() {
  const store = useContext(StoreContext);
  if (!store) throw new Error("useStore outside provider");
  return store;
}

// Selector hook for optimal re-renders
function useSelector<T,>(selector: (state: AppState) => T): T {
  const store = useStore();
  const [selected, setSelected] = useState(() => selector(store.getState()));

  useEffect(() => {
    const handleChange = () => {
      const nextSelected = selector(store.getState());
      setSelected(nextSelected);
    };

    const unsubscribe = store.subscribe(handleChange);
    return unsubscribe;
  }, [store, selector]);

  return selected;
}

// Usage
function UserProfile() {
  const user = useSelector((state) => state.user);
  const { dispatch } = useStore();

  return (
    <div>
      {user ? <h1>{user.name}</h1> : <p>Not logged in</p>}
      <button onClick={() => dispatch({ type: "LOGOUT" })}>
        Logout
      </button>
    </div>
  );
}

<details>

<summary>📚 More Examples</summary>

// Example 1: Middleware pattern with context
type Middleware<S, A> = (store: Store<S, A>) => (next: Dispatch<A>) => Dispatch<A>;

const loggerMiddleware: Middleware<AppState, AppAction> =
  (store) => (next) => (action) => {
    console.log("Dispatching:", action.type);
    console.log("Previous state:", store.getState());
    next(action);
    console.log("Next state:", store.getState());
  };

const persistenceMiddleware: Middleware<AppState, AppAction> =
  (store) => (next) => (action) => {
    next(action);
    localStorage.setItem("appState", JSON.stringify(store.getState()));
  };

// Example 2: AsyncThunk pattern
type AsyncAction<S, A, R = void> = (
  dispatch: Dispatch<A>,
  getState: () => S
) => Promise<R>;

function useAsyncDispatch() {
  const store = useStore();

  return (asyncAction: AsyncAction<AppState, AppAction>) => {
    return asyncAction(
      (action) => store.dispatch(action),
      () => store.getState()
    );
  };
}

// Usage
const loginAsync: AsyncAction<AppState, AppAction> = async (dispatch, getState) => {
  dispatch({ type: "LOGIN_START" });
  try {
    const user = await api.login();
    dispatch({ type: "LOGIN_SUCCESS", payload: user });
  } catch (error) {
    dispatch({ type: "LOGIN_ERROR", payload: error as Error });
  }
};

// Example 3: Thunk context
interface ThunkAction<S, A> {
  (dispatch: Dispatch<A>, getState: () => S): Promise<void> | void;
}

const ThunkContext = createContext<{
  dispatch: (action: AppAction | ThunkAction<AppState, AppAction>) => void;
  getState: () => AppState;
} | undefined>(undefined);

function ThunkProvider({
  children,
  store,
}: {
  children: ReactNode;
  store: Store<AppState, AppAction>;
}) {
  const dispatch = (action: AppAction | ThunkAction<AppState, AppAction>) => {
    if (typeof action === "function") {
      return action(store.dispatch, store.getState);
    }
    return store.dispatch(action);
  };

  return (
    <ThunkContext.Provider value={{ dispatch, getState: store.getState }}>
      {children}
    </ThunkContext.Provider>
  );
}

</details>

🎨 Real-World: Complex Form State with Context

Managing complex form state with validation and async submission:

// Form state machine
interface FormState {
  values: Record<string, any>;
  errors: Record<string, string>;
  touched: Record<string, boolean>;
  isSubmitting: boolean;
  isDirty: boolean;
}

type FormAction =
  | { type: "SET_FIELD"; payload: { name: string; value: any } }
  | { type: "SET_ERROR"; payload: { name: string; error: string } }
  | { type: "TOUCH_FIELD"; payload: string }
  | { type: "START_SUBMIT" }
  | { type: "SUBMIT_SUCCESS" }
  | { type: "SUBMIT_ERROR"; payload: string }
  | { type: "RESET" };

function formReducer(state: FormState, action: FormAction): FormState {
  switch (action.type) {
    case "SET_FIELD":
      return {
        ...state,
        values: { ...state.values, [action.payload.name]: action.payload.value },
        isDirty: true,
      };
    case "SET_ERROR":
      return {
        ...state,
        errors: { ...state.errors, [action.payload.name]: action.payload.error },
      };
    case "TOUCH_FIELD":
      return {
        ...state,
        touched: { ...state.touched, [action.payload]: true },
      };
    case "START_SUBMIT":
      return { ...state, isSubmitting: true };
    case "SUBMIT_SUCCESS":
      return { ...state, isSubmitting: false, isDirty: false };
    case "SUBMIT_ERROR":
      return { ...state, isSubmitting: false };
    case "RESET":
      return {
        values: {},
        errors: {},
        touched: {},
        isSubmitting: false,
        isDirty: false,
      };
    default:
      return state;
  }
}

const FormContext = createContext<
  | {
      state: FormState;
      dispatch: Dispatch<FormAction>;
    }
  | undefined
>(undefined);

function useForm() {
  const context = useContext(FormContext);
  if (!context) throw new Error("useForm outside provider");
  return context;
}

function FormProvider({
  children,
  onSubmit,
}: {
  children: ReactNode;
  onSubmit: (values: Record<string, any>) => Promise<void>;
}) {
  const [state, dispatch] = useReducer(formReducer, {
    values: {},
    errors: {},
    touched: {},
    isSubmitting: false,
    isDirty: false,
  });

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    dispatch({ type: "START_SUBMIT" });
    try {
      await onSubmit(state.values);
      dispatch({ type: "SUBMIT_SUCCESS" });
    } catch (error) {
      dispatch({
        type: "SUBMIT_ERROR",
        payload: (error as Error).message,
      });
    }
  };

  return (
    <FormContext.Provider value={{ state, dispatch }}>
      <form onSubmit={handleSubmit}>{children}</form>
    </FormContext.Provider>
  );
}

// Form field component
function FormField({
  name,
  label,
  type = "text",
}: {
  name: string;
  label: string;
  type?: string;
}) {
  const { state, dispatch } = useForm();
  const value = state.values[name] || "";
  const error = state.errors[name];
  const isTouched = state.touched[name];

  return (
    <div>
      <label>{label}</label>
      <input
        type={type}
        value={value}
        onChange={(e) =>
          dispatch({
            type: "SET_FIELD",
            payload: { name, value: e.target.value },
          })
        }
        onBlur={() => dispatch({ type: "TOUCH_FIELD", payload: name })}
      />
      {isTouched && error && <p>{error}</p>}
    </div>
  );
}

📊 State Management Patterns

PatternUse CaseComplexity
useStateSimple stateLow
useReducerRelated updatesMedium
Redux-likeLarge appsHigh
MobX-likeObservable stateVery High

🔑 Key Takeaways

  • ✅ Use `useReducer` for complex state with multiple related updates
  • ✅ Implement Redux-like patterns for predictable state management
  • ✅ Add middleware for logging, persistence, and side effects
  • ✅ Use selector hooks to optimize re-renders
  • ✅ Separate pure reducers from async operations
  • ✅ Implement error handling in action dispatchers
  • ✅ Use TypeScript discriminated unions for type-safe actions
  • ✅ Consider Redux, Zustand, or Jotai for very large apps

Ready to practice? Challenges | Next: Performance Engineering


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