React Hooks Anti-Pattern'ler ve Çözümleri

React Hooks Anti-Pattern'ler ve Çözümleri
React Hooks, fonksiyonel componentlerde state ve side effect yönetimini mümkün kılan güçlü bir API'dir. Ancak yanlış kullanıldığında performans sorunlarına, sonsuz döngülere ve beklenmeyen davranışlara yol açabilir. Bu rehberde, en yaygın hooks anti-pattern'lerini ve bunların doğru çözümlerini detaylı örneklerle inceleyeceğiz.
Anti-Pattern 1: useEffect İçinde Gereksiz Dependency
useEffect dependency array'inin yanlış yapılandırılması, sonsuz döngülerin ve gereksiz API çağrılarının en yaygın nedenidir.
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
// ❌ KÖTÜ: Object dependency sonsuz döngü oluşturur
function BadComponent({ userId }: { userId: string }) {
const [user, setUser] = useState(null);
// Bu options her render'da yeni bir object!
const options = { headers: { Authorization: 'Bearer token' } };
useEffect(() => {
fetch(`/api/users/${userId}`, options)
.then((res) => res.json())
.then(setUser);
}, [userId, options]); // options her render'da değişir → sonsuz döngü!
return <div>{user?.name}</div>;
}
// ✅ İYİ: Sabit referans kullan
function GoodComponent({ userId }: { userId: string }) {
const [user, setUser] = useState(null);
// useMemo ile sabit referans
const options = useMemo(
() => ({ headers: { Authorization: 'Bearer token' } }),
[] // Sadece bir kez oluşturulur
);
useEffect(() => {
const controller = new AbortController();
fetch(`/api/users/${userId}`, { ...options, signal: controller.signal })
.then((res) => res.json())
.then(setUser)
.catch((err) => {
if (err.name !== 'AbortError') console.error(err);
});
return () => controller.abort(); // Cleanup!
}, [userId, options]);
return <div>{user?.name}</div>;
}
Anti-Pattern 2: State Güncelleme Yarış Koşulu (Race Condition)
// ❌ KÖTÜ: Race condition — eski istek yeni isteğin sonucunu ezebilir
function BadSearch() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
useEffect(() => {
if (!query) return;
// "abc" yazdıktan sonra "ab" araması "abc"den sonra dönebilir!
fetch(`/api/search?q=${query}`)
.then((res) => res.json())
.then(setResults);
}, [query]);
return (
<div>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
{results.map((r: any) => <div key={r.id}>{r.name}</div>)}
</div>
);
}
// ✅ İYİ: AbortController ile race condition önleme
function GoodSearch() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
if (!query) {
setResults([]);
return;
}
const controller = new AbortController();
setIsLoading(true);
fetch(`/api/search?q=${query}`, { signal: controller.signal })
.then((res) => res.json())
.then((data) => {
setResults(data);
setIsLoading(false);
})
.catch((err) => {
if (err.name !== 'AbortError') {
console.error(err);
setIsLoading(false);
}
});
// Sonraki effect çalışmadan önce öncekini iptal et
return () => controller.abort();
}, [query]);
return (
<div>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
{isLoading && <span>Aranıyor...</span>}
{results.map((r: any) => <div key={r.id}>{r.name}</div>)}
</div>
);
}
Anti-Pattern 3: useEffect'te State Senkronizasyonu
// ❌ KÖTÜ: Gereksiz state + useEffect senkronizasyonu
function BadFullName({ firstName, lastName }: { firstName: string; lastName: string }) {
const [fullName, setFullName] = useState('');
// Bu gereksiz! fullName bir türetilmiş (derived) değerdir
useEffect(() => {
setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);
return <span>{fullName}</span>;
}
// ✅ İYİ: Derived state — hesapla, state'e kaydetme
function GoodFullName({ firstName, lastName }: { firstName: string; lastName: string }) {
// Doğrudan hesapla — ekstra render yok!
const fullName = `${firstName} ${lastName}`;
return <span>{fullName}</span>;
}
// Ağır hesaplama gerekiyorsa useMemo kullan
function ExpensiveFullName({ users }: { users: User[] }) {
const sortedUsers = useMemo(
() => [...users].sort((a, b) => a.name.localeCompare(b.name)),
[users]
);
return (
<ul>
{sortedUsers.map((u) => <li key={u.id}>{u.name}</li>)}
</ul>
);
}
Anti-Pattern 4: Stale Closure (Eski Closure Değeri)
// ❌ KÖTÜ: setInterval içinde stale closure
function BadTimer() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
// count her zaman 0! (closure oluşturulduğu andaki değer)
setCount(count + 1); // 0 + 1 = 1, sürekli 1 kalır
}, 1000);
return () => clearInterval(interval);
}, []); // Dependency boş — count değişse bile effect tekrar çalışmaz
return <div>{count}</div>;
}
// ✅ İYİ: Functional updater ile stale closure önleme
function GoodTimer() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
// Functional updater — her zaman güncel değeri kullanır
setCount((prev) => prev + 1);
}, 1000);
return () => clearInterval(interval);
}, []);
return <div>{count}</div>;
}
// ✅ ALTERNATİF: useRef ile güncel değere erişim
function TimerWithRef() {
const [count, setCount] = useState(0);
const countRef = useRef(count);
countRef.current = count; // Her render'da güncelle
useEffect(() => {
const interval = setInterval(() => {
// Ref her zaman güncel değere sahip
console.log('Şu anki count:', countRef.current);
setCount((prev) => prev + 1);
}, 1000);
return () => clearInterval(interval);
}, []);
return <div>{count}</div>;
}
Anti-Pattern 5: Gereksiz useCallback ve useMemo
// ❌ KÖTÜ: Her yerde memoize etmek
function OverMemoized() {
const [name, setName] = useState('');
// Gereksiz — bu fonksiyon sadece input'a geçiyor, child yok
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setName(e.target.value);
}, []);
// Gereksiz — basit string birleştirme
const greeting = useMemo(() => `Merhaba, ${name}!`, [name]);
return (
<div>
<input value={name} onChange={handleChange} />
<p>{greeting}</p>
</div>
);
}
// ✅ İYİ: Sadece gerektiğinde memoize et
function ProperlyOptimized() {
const [name, setName] = useState('');
// Basit handler — memoize etmeye gerek yok
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setName(e.target.value);
};
// Basit hesaplama — memoize etmeye gerek yok
const greeting = `Merhaba, ${name}!`;
return (
<div>
<input value={name} onChange={handleChange} />
<p>{greeting}</p>
</div>
);
}
Sonuç
React Hooks anti-pattern'lerinden kaçınmak, daha performanslı ve öngörülebilir uygulamalar geliştirmenizi sağlar. useEffect'i minimum düzeyde kullanın, race condition'lara karşı AbortController kullanın, derived state için ayrı state oluşturmayın, stale closure'lara dikkat edin ve gereksiz memoizasyondan kaçının. React Compiler çıktığında birçok memoizasyon otomatik olacak olsa da, doğru mental modeli edinmek hala çok önemlidir.