W poprzednich wpisach nauczyliśmy się przekazywać dane przez props – od rodzica do dziecka. To działa świetnie dla prostych przypadków, ale co jeśli mamy głęboką hierarchię komponentów? Co jeśli komponent na 5 poziomie w dół potrzebuje danych z komponentu na samej górze?
Przekazywanie props przez każdy poziom (nawet gdy pośrednie komponenty ich nie potrzebują) to problem znany jako prop drilling. Jest męczący, podatny na błędy i trudny w utrzymaniu. Na szczęście React ma rozwiązanie: Context API.
W tym wpisie poznamy useContext Hook, nauczymy się tworzyć własne konteksty z pełnym TypeScript support, a na końcu odkryjemy moc custom hooks – sposobu na wyciąganie reużywalnej logiki z komponentów. To będzie przełomowy wpis – nauczysz się wzorców, które są fundamentem profesjonalnych aplikacji React!
Problem: Prop Drilling
Zacznijmy od zobaczenia problemu, który Context API rozwiązuje.
// App.tsx
function App() {
const [user, setUser] = useState({ name: 'Paweł', theme: 'dark' });
return <Dashboard user={user} />;
}
// Dashboard.tsx
function Dashboard({ user }) {
return (
<div>
<Sidebar user={user} />
<MainContent user={user} />
</div>
);
}
// Sidebar.tsx
function Sidebar({ user }) {
return (
<div>
<UserWidget user={user} />
<Navigation user={user} />
</div>
);
}
// UserWidget.tsx
function UserWidget({ user }) {
return <div>Witaj, {user.name}!</div>;
}
Widzisz problem? user jest przekazywany przez Dashboard i Sidebar, mimo że te komponenty go nie używają! Są tylko "posłańcami" przekazującymi dane w dół.
Rozwiązanie: Context API
Context API pozwala "wynieść" dane na wyższy poziom i udostępnić je wszystkim komponentom w drzewie, bez przekazywania przez props.
Tworzenie Context
// ThemeContext.tsx
import { createContext, useState, useContext, type ReactNode } from 'react';
type Theme = 'light' | 'dark';
interface ThemeContextType {
theme: Theme;
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
export function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<Theme>('light');
function toggleTheme() {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
}
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
// ✅ Dobrze
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) throw new Error('useTheme must be used within ThemeProvider');
return context;
}
3. TypeScript strict mode
// ✅ Dobrze
const MyContext = createContext<MyType | undefined>(undefined);
Kiedy używać Context vs Props?
Props: Dane potrzebne w bezpośrednim dziecku, 1-2 poziomy
Context: Dane potrzebne w wielu miejscach, prop drilling, globalne dane (theme, auth, cart)
Podsumowanie
Nauczyliśmy się:
✅ Prop drilling – problem i jego skutki
✅ Context API – createContext, Provider, useContext