Skip to main content

Command Palette

Search for a command to run...

11) Typescript Reducer and Context

Updated
  1. Reducer Typing in TypeScript
    A reducer is a function that updates state based on actions.
    In TypeScript, we explicitly type: State, Action, Reducer function
    Define State Type

    type CounterState = {
      count: number;
    };
    

    Define Action Types

    type CounterAction =
      | { type: "INCREMENT" }
      | { type: "DECREMENT" }
      | { type: "RESET"; payload: number };
    

    Create Reducer

    const counterReducer = (
      state: CounterState,
      action: CounterAction
    ): CounterState => {
      switch (action.type) {
        case "INCREMENT":
          return {
            ...state,
            count: state.count + 1,
          };
    
        case "DECREMENT":
          return {
            ...state,
            count: state.count - 1,
          };
    
        case "RESET":
          return {
            ...state,
            count: action.payload,
          };
    
        default:
          return state;
      }
    };
    
  2. Action Typing in TypeScript
    Action typing ensures reducers receive only valid actions.
    Pattern 1: Union Types (Most Common)

    type Action =
      | { type: "ADD_USER"; payload: string }
      | { type: "REMOVE_USER"; payload: number };
    

    Pattern 2: Enum + Interfaces

    enum ActionType {
      ADD = "ADD",
      REMOVE = "REMOVE",
    }
    

    Pattern 3: Generic Action Type

    type Action<T, P> = {
      type: T;
      payload: P;
    };
    
  3. Complete Reducer Example

    type State = {
      users: string[];
    };
    
    type Action =
      | { type: "ADD_USER"; payload: string }
      | { type: "REMOVE_USER"; payload: string };
    
    const reducer = (state: State, action: Action): State => {
      switch (action.type) {
        case "ADD_USER":
          return {
            users: [...state.users, action.payload],
          };
    
        case "REMOVE_USER":
          return {
            users: state.users.filter(
              (user) => user !== action.payload
            ),
          };
    
        default:
          const exhaustiveCheck: never = action;
          return state;
      }
    };
    
  4. Context Typing in TypeScript
    React Context should always be typed. Without typing: dispatch becomes any, state becomes any, unsafe code increases
    Step 1: Define State

    type AuthState = {
      isAuthenticated: boolean;
      user: string | null;
    };
    

    Step 2: Define Actions

    type AuthAction =
      | { type: "LOGIN"; payload: string }
      | { type: "LOGOUT" };
    

    Step 3: Create Reducer

    const authReducer = (
      state: AuthState,
      action: AuthAction
    ): AuthState => {
      switch (action.type) {
        case "LOGIN":
          return {
            ...state,
            isAuthenticated: true,
            user: action.payload,
          };
    
        case "LOGOUT":
          return {
            ...state,
            isAuthenticated: false,
            user: null,
          };
    
        default:
          return state;
      }
    };
    

    Step4: Create Context Type

    type AuthContextType = {
      state: AuthState;
      dispatch: React.Dispatch<AuthAction>;
    };
    

    Step 5: Create Context

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

    Step 6: Create Provider

    const initialState: AuthState = {
      isAuthenticated: false,
      user: null,
    };
    
    export const AuthProvider = ({
      children,
    }: {
      children: React.ReactNode;
    }) => {
      const [state, dispatch] = useReducer(
        authReducer,
        initialState
      );
    
      return (
        <AuthContext.Provider value={{ state, dispatch }}>
          {children}
        </AuthContext.Provider>
      );
    };
    

    Step 7: Create Custom Hook

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

    Step 8: Use Context

    const Login = () => {
      const { state, dispatch } = useAuth();
    
      return (
        <button
          onClick={() =>
            dispatch({
              type: "LOGIN",
              payload: "Rahul",
            })
          }
        >
          Login
        </button>
      );
    };