Hooks y Context Tipados
Tipar correctamente el Context de React requiere un truco importante: createContext necesita un valor inicial, pero no tienes el valor real hasta que el Provider se monta. La solución es createContext<MiContexto | null>(null) y luego crear un custom hook que verifica que el contexto no es null antes de retornarlo. El custom hook retorna MiContexto (no null), y TypeScript lo sabe, dando autocompletado completo en cada componente que lo use.
useReducer con discriminated union actions es el patrón más robusto para estado complejo en TypeScript. Cada action es un objeto con type literal y sus propios campos.
En el switch del reducer, TypeScript hace narrowing automático en cada case: sabe exactamente qué propiedades tiene esa action específica y marca error en compilación si intentas acceder a campos que no existen en ese tipo de action. Agregar un nuevo case sin manejarlo genera un error de exhaustiveness con never.
// useReducer con discriminated union actions
interface State {
contador: number;
cargando: boolean;
error: string | null;
}
type Action =
| { type: "incrementar"; cantidad: number }
| { type: "decrementar" }
| { type: "reset" }
| { type: "setError"; error: string };
function reducer(state: State, action: Action): State {
switch (action.type) {
case "incrementar": return { ...state, contador: state.contador + action.cantidad };
case "decrementar": return { ...state, contador: state.contador - 1 };
case "reset": return { contador: 0, cargando: false, error: null };
case "setError": return { ...state, error: action.error };
default: const _: never = action; return state;
}
}
// Context bien tipado
interface AuthContext {
usuario: Usuario | null;
login: (email: string, pass: string) => Promise<void>;
logout: () => void;
}
const AuthCtx = React.createContext<AuthContext | null>(null);
export function useAuth(): AuthContext {
const ctx = React.useContext(AuthCtx);
if (!ctx) throw new Error("useAuth debe usarse dentro de AuthProvider");
return ctx; // Tipo: AuthContext (no null)
}
// Custom hook tipado
function useFetch<T>(url: string): { data: T | null; loading: boolean; error: string | null } {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetch(url)
.then(r => r.json() as Promise<T>)
.then(setData)
.catch(e => setError(String(e)))
.finally(() => setLoading(false));
}, [url]);
return { data, loading, error };
}
Practica esta lección
Resuelve los ejercicios del curso de TypeScript en la app, gratis y con corrección automática.
Empezar a practicar