11) Typescript Reducer and Context
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 Typetype 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; } };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; };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; } };Context Typing in TypeScript
React Context should always be typed. Without typing: dispatch becomesany, state becomesany, unsafe code increases
Step 1: Define Statetype 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> ); };