Context API is the communication system between distant modules of the spaceship - it allows transmitting data without passing it through every intermediate component. TypeScript ensures that this data always has the correct shape.
1import React, { createContext, useContext, useState } from 'react';
2
3// 1. Define the context type
4interface ThemeContextType {
5 theme: "dark" | "light";
6 toggleTheme: () => void;
7}
8
9// 2. Create context with a default value
10const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
11
12// 3. Custom hook to use context (with validation)
13function useTheme(): ThemeContextType {
14 const context = useContext(ThemeContext);
15 if (context === undefined) {
16 throw new Error("useTheme must be used inside ThemeProvider");
17 }
18 return context;
19}
20
21// 4. Provider component
22function ThemeProvider({ children }: { children: React.ReactNode }) {
23 const [theme, setTheme] = useState<"dark" | "light">("dark");
24
25 const toggleTheme = () => {
26 setTheme(prev => prev === "dark" ? "light" : "dark");
27 };
28
29 return (
30 <ThemeContext.Provider value={{ theme, toggleTheme }}>
31 {children}
32 </ThemeContext.Provider>
33 );
34}1// Pattern: createContext<T | undefined>(undefined)
2// + custom hook with validation
3
4// ADVANTAGES:
5// - Runtime error if component is not inside a Provider
6// - Type in component is always ThemeContextType (not undefined)
7// - Safer than providing an "empty" default object
8
9// ALTERNATIVE (less safe):
10const ThemeContext = createContext<ThemeContextType>({
11 theme: "dark",
12 toggleTheme: () => {} // empty function - silent bug
13});useReducer is ideal for managing complex state. TypeScript makes every action and state change safe:
1// 1. Define the state type
2interface MissionState {
3 missions: Mission[];
4 selectedMission: Mission | null;
5 loading: boolean;
6 error: string | null;
7}
8
9interface Mission {
10 id: string;
11 name: string;
12 status: "planned" | "active" | "completed";
13}
14
15// 2. Define action types - discriminated union!
16type MissionAction =
17 | { type: "SET_MISSIONS"; payload: Mission[] }
18 | { type: "SELECT_MISSION"; payload: string }
19 | { type: "ADD_MISSION"; payload: Mission }
20 | { type: "UPDATE_STATUS"; payload: { id: string; status: Mission["status"] } }
21 | { type: "SET_LOADING"; payload: boolean }
22 | { type: "SET_ERROR"; payload: string | null };
23
24// 3. Reducer with full typing
25function missionReducer(state: MissionState, action: MissionAction): MissionState {
26 switch (action.type) {
27 case "SET_MISSIONS":
28 return { ...state, missions: action.payload, loading: false };
29 case "SELECT_MISSION":
30 return {
31 ...state,
32 selectedMission: state.missions.find(m => m.id === action.payload) || null
33 };
34 case "ADD_MISSION":
35 return { ...state, missions: [...state.missions, action.payload] };
36 case "UPDATE_STATUS":
37 return {
38 ...state,
39 missions: state.missions.map(m =>
40 m.id === action.payload.id
41 ? { ...m, status: action.payload.status }
42 : m
43 )
44 };
45 case "SET_LOADING":
46 return { ...state, loading: action.payload };
47 case "SET_ERROR":
48 return { ...state, error: action.payload, loading: false };
49 default:
50 return state;
51 }
52}1// Initial state
2const initialState: MissionState = {
3 missions: [],
4 selectedMission: null,
5 loading: false,
6 error: null
7};
8
9// Context type
10interface MissionContextType {
11 state: MissionState;
12 dispatch: React.Dispatch<MissionAction>;
13}
14
15const MissionContext = createContext<MissionContextType | undefined>(undefined);
16
17// Custom hook
18function useMissions(): MissionContextType {
19 const context = useContext(MissionContext);
20 if (!context) {
21 throw new Error("useMissions must be used inside MissionProvider");
22 }
23 return context;
24}
25
26// Provider
27function MissionProvider({ children }: { children: React.ReactNode }) {
28 const [state, dispatch] = useReducer(missionReducer, initialState);
29
30 return (
31 <MissionContext.Provider value={{ state, dispatch }}>
32 {children}
33 </MissionContext.Provider>
34 );
35}1function MissionList() {
2 const { state, dispatch } = useMissions();
3
4 if (state.loading) return <p>Loading missions...</p>;
5 if (state.error) return <p>Error: {state.error}</p>;
6
7 return (
8 <div>
9 {state.missions.map(mission => (
10 <div key={mission.id} onClick={() =>
11 dispatch({ type: "SELECT_MISSION", payload: mission.id })
12 }>
13 <h3>{mission.name}</h3>
14 <span>{mission.status}</span>
15 </div>
16 ))}
17 </div>
18 );
19}