createContext потребує значення за замовчуванням. Для контексту з реальними даними (поточний користувач, кошик) осмисленого значення за замовчуванням немає, тому часто пишуть null:
type AuthContextValue = {
user: User;
logout: () => void;
};
const AuthContext = createContext<AuthContextValue | null>(null);
Тепер кожен useContext(AuthContext) повертає AuthContextValue | null, і в кожному компоненті потрібна перевірка. Набридливо - і перевірка однаково нічого не робить, бо провайдер завжди є.
Рішення - власний хук з перевіркою в одному місці:
export function useAuth(): AuthContextValue {
const context = useContext(AuthContext);
if (context === null) {
throw new Error('useAuth треба викликати всередині <AuthProvider>');
}
return context;
}
function Header() {
const { user, logout } = useAuth(); // AuthContextValue без null
return <button onClick={logout}>Вийти, {user.name}</button>;
}
Переваги:
- тип без
nullу компонентах; - зрозуміла помилка одразу, якщо компонент випадково відрендерили поза провайдером (замість
Cannot read properties of nullдесь далі); - контекст можна не експортувати - лише хук і провайдер. Так менше способів використати його неправильно.
Провайдер разом із логікою:
export function AuthProvider({ user, children }: { user: User; children: ReactNode }) {
const logout = useCallback(() => router.post('/logout'), []);
const value = useMemo(() => ({ user, logout }), [user, logout]);
return <AuthContext value={value}>{children}</AuthContext>;
}
У React 19 контекст можна рендерити напряму як провайдер - <AuthContext value={...}> замість <AuthContext.Provider value={...}>.
Альтернатива, коли розумне значення за замовчуванням є (тема, мова): createContext<Theme>('light') - перевірки на null не потрібні взагалі.
Пастка: createContext<AuthContextValue>(null!) чи {} as AuthContextValue - «заглушка» для компілятора. TypeScript замовкне, але компонент поза провайдером отримає null чи порожній об'єкт і впаде з незрозумілою помилкою під час виконання.