Skip to content

TypeScript State

TypeScript ensures state shape consistency across setters, reducers, and selectors. This lesson types local state, reducers, context values, and global stores.

Typing useState and useReducer

useState<User | null>(null) explicit generic when inference fails. useReducer<State, Action>(reducer, initialState) types both state and action union.

Define action types as discriminated union for exhaustive switch checking in reducers.

type State = { count: number };
type Action =
  | { type: 'increment' }
  | { type: 'add'; payload: number };

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'increment': return { count: state.count + 1 };
    case 'add': return { count: state.count + action.payload };
    default: {
      const _exhaustive: never = action;
      return state;
    }
  }
}

never in default case ensures all actions handled — compile error if new action added.

Context and Store Typing

interface AuthContextValue {
  user: User | null;
  login: (credentials: Credentials) => Promise<void>;
}
const AuthContext = createContext<AuthContextValue | null>(null);
  • Type context null default + throw in hook for safety.
  • Zustand: create<StoreState>()((set) => ({ ... }))
  • Redux Toolkit infers types from slice — export RootState, AppDispatch.
  • Use Zod to validate API data before setting typed state.

State Typing Reference

Type patterns for React state layers.

API Typing Approach
useState Generic or inferred
useReducer State + Action union
Context Interface for value
Zustand Interface on create<>
Redux RootState, AppDispatch types
TanStack Query Generic on useQuery<T>

Typed Redux Hooks

Export typed useAppDispatch and useAppSelector wrapping generic hooks with your store types.

export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
export const useAppDispatch = () => useDispatch<AppDispatch>();

Zod for Runtime + Types

Infer TypeScript types from Zod schemas parsing API responses — single source of truth for runtime validation and static types.

const UserSchema = z.object({ id: z.string(), name: z.string() });
type User = z.infer<typeof UserSchema>;

Common Mistakes

  • Untyped context defaulting to any.
  • Action types as string without discriminated union.
  • Not typing async error states.
  • Assertions (as User) without validation on API data.

Key Takeaways

  • Type state, actions, and context values explicitly.
  • Discriminated unions enable exhaustive reducer checks.
  • Infer store types from Redux Toolkit slices.
  • Validate external data with Zod before typing state.

Pro Tip

Enable noUncheckedIndexedAccess — safer array/object access in reducer logic.