useContext ile Global State Yönetimi: Adım Adım Rehber

useContext ile Global State Yönetimi: Adım Adım Rehber
React uygulamalarında prop drilling problemi, bileşen ağacının derinliklerine veri aktarırken ortaya çıkar. Her seviyedeki bileşen, kullanmadığı halde props'ları bir sonraki seviyeye iletmek zorunda kalır. useContext hook'u ve Context API, bu sorunu çözerek bileşen ağacının herhangi bir noktasından doğrudan veriye erişmenizi sağlar. Bu rehberde Context API'nin kurulumunu, kullanımını ve ileri seviye pattern'lerini adım adım öğreneceksiniz.
Context API Nedir?
Context, bileşen ağacı boyunca veriyi "teleport" etmenizi sağlayan bir mekanizmadır. Tema, dil, oturum bilgisi gibi "global" verileri yönetmek için idealdir.
Context API, Redux veya Zustand gibi state management kütüphanelerinin yerini almak için tasarlanmamıştır. Sık güncellenen veriler için performans sorunlarına yol açabilir. En iyi kullanım alanı, nadiren değişen ve geniş kapsamda ihtiyaç duyulan verilerdir.
Adım 1: Context Oluşturma
// context/ThemeContext.js
import { createContext, useContext, useState, useMemo } from 'react';
// 1. Context oluşturma
const ThemeContext = createContext(null);
// 2. Provider bileşeni
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
// useMemo ile gereksiz re-render'ları önleme
const value = useMemo(() => ({
theme,
toggleTheme,
isDark: theme === 'dark'
}), [theme]);
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}
// 3. Custom hook ile kolay erişim
export function useTheme() {
const context = useContext(ThemeContext);
if (context === null) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
}
İpucu: Context için her zaman bir custom hook oluşturun. Bu, hem hata kontrolü sağlar hem de consumer bileşenlerin doğrudan Context objesine bağımlılığını kaldırır. Böylece Context implementasyonunu değiştirirken consumer bileşenleri etkilemezsiniz.
Adım 2: Provider'ı Yerleştirme
// App.jsx
import { ThemeProvider } from './context/ThemeContext';
import { AuthProvider } from './context/AuthContext';
function App() {
return (
<AuthProvider>
<ThemeProvider>
<div className="app">
<Header />
<Main />
<Footer />
</div>
</ThemeProvider>
</AuthProvider>
);
}
Adım 3: Context'i Kullanma
// components/Header.jsx
import { useTheme } from '../context/ThemeContext';
function Header() {
const { theme, toggleTheme, isDark } = useTheme();
return (
<header className={`header header-${theme}`}>
<h1>Uygulamam</h1>
<button onClick={toggleTheme}>
{isDark ? '☀️ Açık Tema' : '🌙 Koyu Tema'}
</button>
</header>
);
}
Gerçek Dünya Örneği: Auth Context
// context/AuthContext.jsx
import { createContext, useContext, useState, useEffect, useMemo, useCallback } from 'react';
const AuthContext = createContext(null);
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
// Sayfa yüklendiğinde oturum kontrolü
useEffect(() => {
const checkAuth = async () => {
try {
const token = localStorage.getItem('token');
if (!token) {
setLoading(false);
return;
}
const response = await fetch('/api/auth/me', {
headers: { Authorization: `Bearer ${token}` }
});
if (response.ok) {
const userData = await response.json();
setUser(userData);
} else {
localStorage.removeItem('token');
}
} catch (error) {
console.error('Auth kontrol hatası:', error);
} finally {
setLoading(false);
}
};
checkAuth();
}, []);
const login = useCallback(async (email, password) => {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
if (!response.ok) {
throw new Error('Giriş başarısız');
}
const { token, user: userData } = await response.json();
localStorage.setItem('token', token);
setUser(userData);
}, []);
const logout = useCallback(() => {
localStorage.removeItem('token');
setUser(null);
}, []);
const value = useMemo(() => ({
user,
loading,
login,
logout,
isAuthenticated: !!user
}), [user, loading, login, logout]);
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
}
Birden Fazla Context Birleştirme
// Birden fazla Provider'ı düzenli şekilde birleştirme
function AppProviders({ children }) {
return (
<AuthProvider>
<ThemeProvider>
<LanguageProvider>
<NotificationProvider>
{children}
</NotificationProvider>
</LanguageProvider>
</ThemeProvider>
</AuthProvider>
);
}
// Kullanım
function App() {
return (
<AppProviders>
<Router />
</AppProviders>
);
}
Uyarı: Context value değiştiğinde, o Context'i kullanan tüm bileşenler yeniden render olur. Bu nedenle sık güncellenen veriler (mouse pozisyonu, scroll pozisyonu, anlık form değerleri) için Context kullanmaktan kaçının. Bu tür veriler için useRef, state lifting veya harici state management çözümleri tercih edin.
Context Performans Optimizasyonu
// KÖTÜ: Her render'da yeni obje oluşturulur
function BadProvider({ children }) {
const [count, setCount] = useState(0);
return (
<MyContext.Provider value={{ count, setCount }}>
{children}
</MyContext.Provider>
);
}
// İYİ: useMemo ile memoize edilmiş value
function GoodProvider({ children }) {
const [count, setCount] = useState(0);
const value = useMemo(() => ({
count,
increment: () => setCount(prev => prev + 1),
decrement: () => setCount(prev => prev - 1)
}), [count]);
return (
<MyContext.Provider value={value}>
{children}
</MyContext.Provider>
);
}
// EN İYİ: State ve dispatch'i ayırma
const CountStateContext = createContext(null);
const CountDispatchContext = createContext(null);
function CountProvider({ children }) {
const [count, setCount] = useState(0);
return (
<CountStateContext.Provider value={count}>
<CountDispatchContext.Provider value={setCount}>
{children}
</CountDispatchContext.Provider>
</CountStateContext.Provider>
);
}
Bilgi: State ve dispatch (güncelleme fonksiyonları) için ayrı Context'ler oluşturmak, yalnızca dispatch kullanan bileşenlerin state değiştiğinde gereksiz yere render olmasını engeller.
Sonuç
useContext ve Context API, prop drilling sorununu zarif bir şekilde çözer ve tema, dil, oturum gibi global verilerin yönetimini kolaylaştırır. Ancak her şeyi Context'e koymaktan kaçının. Performans kritik uygulamalarda useMemo ile value memoizasyonu, state/dispatch ayrımı ve gerektiğinde harici state management çözümleri kullanmayı değerlendirin.