React Hooks ile Başlangıç Hataları: Kapsamlı Rehber
React Hooks, fonksiyonel bileşenlerde state ve yaşam döngüsü özelliklerini kullanmamızı sağlayarak React geliştirme deneyimini kökten değiştirdi. Ancak, bu güçlü araçlar başlangıç seviyesi geliştiriciler için bazı tuzaklar da barındırır. Bu kapsamlı rehberde, React Hooks kullanırken en sık yapılan 5 hatayı inceleyecek, bu hatalardan nasıl kaçınacağınızı ve daha sağlam, performanslı uygulamalar geliştirmek için en iyi uygulamaları öğreneceksiniz. Hazır mısınız? Gelin, Hooks dünyasına dalalım ve yaygın engelleri birlikte aşalım.
React Hooks, 2019 yılında React 16.8 ile birlikte tanıtıldı ve o günden bu yana React geliştirme paradigmalarını önemli ölçüde etkiledi. Daha önce sadece sınıf tabanlı bileşenlerde erişilebilen state yönetimi ve yaşam döngüsü metodları gibi özelliklerin, fonksiyonel bileşenlerde de kullanılabilmesini sağladı. Bu, daha temiz, daha anlaşılır ve daha yeniden kullanılabilir kod yazmanın kapılarını araladı. Örneğin, useState ile bir bileşenin içindeki durumu kolayca yönetebilirken, useEffect ile side effect’leri (veri çekme, DOM manipülasyonu, abonelikler vb.) kontrol altına alabiliyoruz. Ancak, bu güçlü araçlar beraberinde bazı öğrenme eğrileri de getiriyor.
Peki, React Hooks neden başlangıç seviyesi geliştiriciler için bu kadar zorlayıcı olabiliyor? Temelde, Hooks, React’in dahili çalışma prensipleriyle (örneğin, render döngüsü, referans eşitliği) derinlemesine bağlantılıdır. Geleneksel sınıf bileşenlerindeki yaşam döngüsü metodlarına alışkın olan geliştiriciler, Hooks’un daha deklaratif ve fonksiyonel yaklaşımına adapte olmakta zorlanabilirler. Özellikle useEffect‘in bağımlılık dizisi ve kapanışlar (closures) kavramları, React’in nasıl yeniden render edildiğini ve state’i nasıl yönettiğini tam olarak anlamadan yanlış kullanımlara yol açabiliyor. Bu durum, beklenmedik davranışlara, performans sorunlarına veya sonsuz döngülere neden olabilir. Ayrıca, Hooks’un belirli kuralları vardır (örneğin, Hooks’u sadece fonksiyonel bileşenlerin veya özel Hooks’ların en üst seviyesinde çağırmak gibi) ve bu kurallara uyulmaması hatalara davetiye çıkarır.
Bu makalede, React Hooks ile ilgili en yaygın 5 hatayı ele alarak, bu kavramları daha derinlemesine anlamanıza yardımcı olmayı hedefliyoruz. Her bir hata için gerçek dünya senaryolarına dayalı vaka analizleri sunacak, doğru ve yanlış kullanımları kod örnekleriyle açıklayacağız. Amacımız, bu tuzaklardan kaçınmanızı sağlayacak bilgi ve pratik ipuçları sunarak, React Hooks’u ustaca kullanmanıza yardımcı olmaktır. Bu yolculuk boyunca, sadece hataları tanımlamakla kalmayacak, aynı zamanda onların altında yatan temel React prensiplerini de keşfedeceğiz. Unutmayın, hatalar öğrenme sürecinin doğal bir parçasıdır ve onları anlayarak çok daha güçlü bir geliştirici olabilirsiniz.
useState’i Yanlış Kullanıyor Olabilir Misiniz? State Güncellemelerinin İnce Detayları
useState, React Hooks’un temel taşlarından biridir ve fonksiyonel bileşenlerde state yönetimi için kullanılır. Basit gibi görünse de, özellikle state güncellemeleri söz konusu olduğunda bazı incelikleri vardır. Başlangıç seviyesi geliştiricilerin yaptığı en yaygın hatalardan biri, state’i doğrudan mutate etmeye çalışmak veya güncellemelerin eşzamansız (asynchronous) doğasını yanlış anlamaktır.
Öncelikle, useState‘in nasıl çalıştığını hatırlayalım. Bir useState çağrısı iki elemanlı bir dizi döndürür: güncel state değeri ve bu değeri güncelleyen bir fonksiyon. Örneğin, const [count, setCount] = useState(0); şeklinde bir kullanım, count değerini ve setCount fonksiyonunu bize verir. Önemli nokta şudur ki, React’teki state değerleri immutable (değişmez) olmalıdır. Yani, bir state’i güncellemek istediğinizde, mevcut state üzerinde değişiklik yapmak yerine, state’in yeni bir kopyasını oluşturup bu kopyayı döndürmeniz gerekir. Bu prensip, React’in bileşenleri ne zaman yeniden render edeceğine karar vermesini kolaylaştırır ve beklenmedik yan etkilerin önüne geçer.
Yaygın Hata: State’i Doğrudan Değiştirmek
Bir nesne veya dizi state’i tutuyorken, doğrudan o state’in özelliklerini değiştirmeye çalışmak büyük bir hatadır. React, referans eşitliğini kullanarak state’in değişip değişmediğini kontrol eder. Eğer mevcut state nesnesiyle yeni state nesnesi aynı referansa sahipse, React güncellemeyi algılamaz ve bileşen yeniden render edilmez. İşte yanlış bir örnek:
function YanlisProfilGuncelleme() {
const [user, setUser] = useState({ name: 'Ali', age: 30 });
const handleUpdateAge = () => {
// YANLIŞ: State doğrudan değiştiriliyor
user.age += 1;
setUser(user); // React bu değişikliği algılamayabilir!
console.log("Yanlış Güncelleme:", user);
};
return (
Ad: {user.name}, Yaş: {user.age}
);
}
Yukarıdaki örnekte, user.age += 1; satırı mevcut user nesnesini doğrudan değiştirir. Ardından setUser(user); çağrısı yapılsa bile, user nesnesinin referansı değişmediği için React, state'in aslında değiştiğini düşünmeyebilir ve bileşeni yeniden render etmeyebilir veya beklenmedik davranışlar sergileyebilir. Doğru yaklaşım, her zaman state'in yeni bir kopyasını oluşturmaktır.
Doğru Yaklaşım: Immutable Güncellemeler
Bir nesne state'i güncellerken spread operatörü (...) kullanarak mevcut özelliklerini kopyalayıp, ardından sadece değiştirmek istediğiniz özellikleri belirtmelisiniz. Diziler için de benzer şekilde, slice, map veya spread operatörü gibi yöntemlerle yeni bir dizi oluşturmalısınız.
function DogruProfilGuncelleme() {
const [user, setUser] = useState({ name: 'Ali', age: 30 });
const handleUpdateAge = () => {
// DOĞRU: Mevcut state'in yeni bir kopyasını oluştur
setUser(prevUser => ({
...prevUser,
age: prevUser.age + 1
}));
};
const handleUpdateName = () => {
// Başka bir örnek: name güncelleme
setUser(prevUser => ({
...prevUser,
name: prevUser.name === 'Ali' ? 'Ayşe' : 'Ali'
}));
};
return (
Ad: {user.name}, Yaş: {user.age}
);
}
Burada, setUser(prevUser => ({ ...prevUser, age: prevUser.age + 1 })); fonksiyonel bir güncelleme kullanıyoruz. prevUser parametresi, o anki güncel state değerini temsil eder. Yeni bir nesne oluşturup, önceki tüm özellikleri kopyalayarak ve sadece age özelliğini güncelleyerek, React'in state değişikliğini doğru bir şekilde algılamasını sağlıyoruz. Bu yaklaşım, state'in immutable doğasını korur ve tahmin edilebilir davranışlar sunar.
Vaka Analizi: Bir E-ticaret Sepeti Uygulaması
Bir e-ticaret uygulamasında sepetinizdeki ürünlerin miktarını güncellediğinizi düşünün. Sepetiniz const [cartItems, setCartItems] = useState([]); şeklinde bir dizi içinde tutuluyor olabilir. Eğer bir ürünün miktarını artırmak istiyorsanız ve doğrudan dizideki objeyi mutate ederseniz, React bu değişikliği algılamayacaktır.
// Yanlış Yaklaşım:
const updateQuantityWrong = (productId, newQuantity) => {
const itemToUpdate = cartItems.find(item => item.id === productId);
if (itemToUpdate) {
itemToUpdate.quantity = newQuantity; // Doğrudan mutasyon!
setCartItems(cartItems); // Referans aynı, React algılamaz
}
};
// Doğru Yaklaşım:
const updateQuantityCorrect = (productId, newQuantity) => {
setCartItems(prevItems =>
prevItems.map(item =>
item.id === productId ? { ...item, quantity: newQuantity } : item
)
);
};
Doğru yaklaşımda, map fonksiyonunu kullanarak yeni bir dizi oluşturuyor ve sadece ilgili ürünün miktarını güncellemek için yeni bir obje oluşturuyoruz. Bu, hem state'in immutable kalmasını sağlar hem de React'in UI'ı doğru bir şekilde güncellemesini garantiler. Fonksiyonel güncelleme (prevItems => ...) aynı zamanda state güncellemelerinin eşzamansız olabileceği durumlar için de en güvenli yöntemdir, çünkü her zaman en güncel state değerine erişmenizi sağlar.
useEffect Bağımlılıklarını Doğru Yönetmek: Sonsuz Döngülerden Nasıl Kurtulursunuz?
useEffect, React'teki fonksiyonel bileşenlerde "yan etkileri" (side effects) yönetmek için kullanılan bir Hook'tur. Veri çekme, abonelikler, manuel DOM değişiklikleri, zamanlayıcılar gibi işlemler useEffect içinde gerçekleştirilir. Ancak, useEffect'in doğru kullanımı, özellikle bağımlılık dizisi (dependency array) konusunda başlangıç seviyesi geliştiriciler için kafa karıştırıcı olabilir. Bağımlılık dizisini yanlış yönetmek, gereksiz yeniden render'lara, performans sorunlarına veya daha da kötüsü, sonsuz döngülere yol açabilir.
useEffect iki argüman alır: bir callback fonksiyonu ve isteğe bağlı bir bağımlılık dizisi. Callback fonksiyonu, bileşen render edildikten sonra çalışır. Eğer bağımlılık dizisi sağlanmazsa, callback her render işleminden sonra çalışır. Boş bir dizi ([]) sağlanırsa, callback sadece bileşen monte edildiğinde (mount) bir kez çalışır ve demonte edildiğinde (unmount) cleanup fonksiyonu varsa çalışır. Bağımlılık dizisine belirli değişkenler eklendiğinde ise, bu değişkenlerden herhangi biri değiştiğinde callback yeniden çalışır. Buradaki ana mantık, useEffect içindeki kodun ne zaman tekrar çalışması gerektiğini React'e bildirmektir.
Yaygın Hata: Bağımlılıkları Eksik Bırakmak veya Yanlış Eklemek
Bir useEffect içinde kullanılan ancak bağımlılık dizisine eklenmeyen bir değişken veya fonksiyon, kapanış (closure) nedeniyle ilk render'daki değerini koruyabilir ve bu durum güncel olmayan (stale) verilere yol açabilir. Örneğin, bir sayaç uygulamasında:
function YanlisSayaç() {
const [count, setCount] = useState(0);
useEffect(() => {
// YANLIŞ: count bağımlılık dizisinde değil
const interval = setInterval(() => {
console.log('Count:', count); // Her zaman 0 gösterir!
setCount(count + 1);
}, 1000);
return () => clearInterval(interval);
}, []); // Boş bağımlılık dizisi, count'ın ilk değerini yakalar
return Sayaç: {count}
;
}
Bu örnekte, setInterval içindeki count değeri, useEffect ilk kez çalıştığında (bileşen mount edildiğinde) 0 olarak "yakalanır". Bağımlılık dizisi boş olduğu için, useEffect bir daha çalışmaz ve count her zaman 0 olarak kalır. setCount(count + 1) çağrılsa bile, hep 0 + 1 işlemini yapar ve sayaç sadece 1'e kadar çıkar. Bu durum sonsuz döngüye yol açmaz, ancak beklenen davranışı sergilemez.
Sonsuz Döngüye Yol Açan Hata: Obje Referansları
Daha tehlikeli bir hata, bağımlılık dizisine doğrudan bir nesne veya fonksiyon tanımladığınızda ortaya çıkar. Her render'da yeni bir referans değeri oluştuğu için, useEffect sürekli olarak bu "değişikliği" algılar ve tekrar çalışır, bu da sonsuz bir döngüye neden olur.
function YanlisVeriCekme() {
const [data, setData] = useState(null);
const [query, setQuery] = useState('react');
// YANLIŞ: options nesnesi her render'da yeniden oluşturulur
const options = { method: 'GET', headers: { 'Content-Type': 'application/json' } };
useEffect(() => {
console.log('Veri çekiliyor...');
// Veri çekme işlemi (API çağrısı gibi)
fetch(https://api.example.com/search?q=${query}, options)
.then(res => res.json())
.then(json => setData(json));
}, [query, options]); // options her render'da yeni bir referanstır!
return (
setQuery(e.target.value)} />
Gelen veri: {data ? JSON.stringify(data) : 'Yükleniyor...'}
);
}
Yukarıdaki örnekte, options nesnesi her render'da yeniden oluşturulduğu için, useEffect'in bağımlılık dizisinde her zaman "değişmiş" olarak algılanır. Bu da useEffect'in sonsuz bir döngüye girmesine ve sürekli veri çekmeye çalışmasına neden olur.
Doğru Yaklaşım: Bağımlılıkları Yönetmek
Doğru yaklaşım, useEffect'in callback'i içinde kullanılan ve değiştiğinde yan etkinin tekrar çalışmasını istediğimiz tüm değişkenleri ve fonksiyonları bağımlılık dizisine eklemektir. Eğer bir state güncelleme fonksiyonu (setCount gibi) kullanılıyorsa, React bu fonksiyonun referansının sabitleştiğini garanti ettiği için bağımlılık dizisine eklenmesine gerek yoktur. Fonksiyonel güncellemeler, kapanış sorunlarını çözmek için de harika bir yoldur.
function DogruSayaç() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
// DOĞRU: Fonksiyonel güncelleme ile güncel state'e erişim
setCount(prevCount => prevCount + 1);
}, 1000);
return () => clearInterval(interval);
}, []); // Boş bağımlılık dizisi, çünkü setCount'un referansı değişmez
return Sayaç: {count}
;
}
Bu sayaç örneğinde, setCount(prevCount => prevCount + 1) kullanarak, setInterval callback'i her çalıştığında en güncel count değerine erişiyoruz. Bu sayede, useEffect'in bağımlılık dizisine count eklememize gerek kalmaz ve kapanış kaynaklı sorunları aşarız.
Sonsuz döngü örneği için ise, useMemo veya useCallback kullanarak nesne veya fonksiyon referanslarının sabit kalmasını sağlamak gerekir:
import React, { useState, useEffect, useMemo } from 'react';
function DogruVeriCekme() {
const [data, setData] = useState(null);
const [query, setQuery] = useState('react');
// DOĞRU: useMemo ile options nesnesinin referansını sabitle
const options = useMemo(() => ({
method: 'GET',
headers: { 'Content-Type': 'application/json' }
}), []); // Bağımlılık yok, bir kez oluşturulur
useEffect(() => {
console.log('Veri çekiliyor (Doğru)...');
const fetchData = async () => {
try {
const response = await fetch(https://api.example.com/search?q=${query}, options);
const json = await response.json();
setData(json);
} catch (error) {
console.error("Veri çekme hatası:", error);
}
};
if (query) { // Sadece bir sorgu varsa çek
fetchData();
}
}, [query, options]); // query değiştiğinde useEffect tekrar çalışır, options referansı sabittir.
return (
setQuery(e.target.value)} />
Gelen veri: {data ? JSON.stringify(data) : 'Yükleniyor...'}
);
}
Burada useMemo kullanarak options nesnesinin sadece bir kez oluşturulmasını sağlıyoruz. Böylece, options'ın referansı her render'da değişmez ve useEffect sadece query değiştiğinde yeniden çalışır. Bu, sonsuz döngü problemini çözer ve performansı artırır.
Her Şeyi Tek Bir useEffect'e Mi Tıkıyorsunuz? Etkili Yan Etki Yönetimi
React Hooks ile çalışmaya başlayan geliştiricilerin yaptığı bir başka yaygın hata, tüm yan etkilerini tek bir büyük useEffect Hook'unun içine sığdırmaya çalışmaktır. Geleneksel sınıf tabanlı bileşenlerdeki componentDidMount, componentDidUpdate ve componentWillUnmount gibi yaşam döngüsü metodlarını tek bir useEffect içinde taklit etme eğilimi vardır. Ancak bu yaklaşım, kodun okunabilirliğini, yönetilebilirliğini ve test edilebilirliğini ciddi şekilde azaltır.
Tek bir useEffect içinde birden fazla, birbiriyle ilgisiz yan etkiyi birleştirmek, özellikle bağımlılık dizisini doğru bir şekilde yönetmeyi çok zorlaştırır. Her bir yan etki farklı bağımlılıklara sahip olabilir ve bu bağımlılıkların tamamını tek bir diziye eklemek, Hook'un gereğinden fazla çalışmasına neden olabilir. Bu durum hem performans sorunlarına yol açar hem de hangi değişikliğin hangi yan etkiyi tetiklediğini anlamayı zorlaştırır.
Yaygın Hata: Monolitik useEffect
Bir e-ticaret sayfasında ürün listesini çekmek, kullanıcı aktivitelerini loglamak ve bir bileşen mounted edildiğinde bir animasyonu tetiklemek gibi farklı işlevleri tek bir useEffect içinde topladığınızı hayal edin:
function YanlisMonolitikKomponent() {
const [products, setProducts] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [searchTerm, setSearchTerm] = useState('');
useEffect(() => {
// YANLIŞ: Tüm yan etkiler tek bir yerde
// 1. Ürün verisi çekme (currentPage, searchTerm'e bağlı)
const fetchProducts = async () => {
console.log(Ürünler çekiliyor: Sayfa ${currentPage}, Arama: ${searchTerm});
const response = await fetch(/api/products?page=${currentPage}&search=${searchTerm});
const data = await response.json();
setProducts(data);
};
fetchProducts();
// 2. Kullanıcı etkinliğini loglama (Hiçbir şeye bağlı değil, sadece mount'ta loglanmalı)
console.log('Kullanıcı ana sayfaya giriş yaptı.');
// 3. Bir animasyon başlatma (Sadece mount'ta olmalı)
const animationTimer = setTimeout(() => {
console.log('Hoş geldiniz animasyonu başlatıldı!');
}, 1000);
// Temizleme fonksiyonu:
return () => {
clearTimeout(animationTimer);
// Diğer temizlikler...
};
}, [currentPage, searchTerm]); // Tüm bağımlılıklar burada
return (
Ürün Listesi
setSearchTerm(e.target.value)}
placeholder="Ürün Ara..."
/>
{products.map(product => (
- {product.name}
))}
);
}
Yukarıdaki örnekte, currentPage veya searchTerm değiştiğinde, sadece ürün verisinin çekilmesi gerekirken, "Kullanıcı ana sayfaya giriş yaptı." mesajı yeniden loglanır ve "Hoş geldiniz animasyonu başlatıldı!" mesajı da gereksiz yere tekrar çalışır. Bu, kaynak israfına ve beklenmedik davranışlara neden olur. Her bir yan etkinin kendi tetikleyicileri ve yaşam döngüsü kuralları olması gerekirken, hepsi aynı bağımlılıklar kümesine bağlıdır.
Doğru Yaklaşım: Yan Etkileri Ayırmak
Her bir yan etkiyi kendi bağımsız useEffect Hook'una ayırmak, kodunuzu çok daha düzenli, anlaşılır ve bakımı kolay hale getirir. Her useEffect Hook'u, kendi özel sorumluluğunu üstlenir ve yalnızca kendi bağımlılıkları değiştiğinde yeniden çalışır.
import React, { useState, useEffect } from 'react';
function DogruAyrıkKomponent() {
const [products, setProducts] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [searchTerm, useStateSearchTerm] = useState('');
// 1. Yan Etki: Ürün verisi çekme
useEffect(() => {
const fetchProducts = async () => {
console.log(Ürünler çekiliyor: Sayfa ${currentPage}, Arama: ${searchTerm});
try {
const response = await fetch(/api/products?page=${currentPage}&search=${searchTerm});
const data = await response.json();
setProducts(data);
} catch (error) {
console.error("Ürün çekme hatası:", error);
}
};
fetchProducts();
}, [currentPage, searchTerm]); // Sadece currentPage veya searchTerm değiştiğinde çalışır
// 2. Yan Etki: Kullanıcı etkinliğini loglama (sadece mount'ta)
useEffect(() => {
console.log('Kullanıcı ana sayfaya giriş yaptı.');
// Bu sadece bir kez çalışır, çünkü bağımlılık dizisi boş
}, []);
// 3. Yan Etki: Bir animasyon başlatma (sadece mount'ta, temizleme ile)
useEffect(() => {
const animationTimer = setTimeout(() => {
console.log('Hoş geldiniz animasyonu başlatıldı!');
}, 1000);
return () => {
console.log('Animasyon zamanlayıcısı temizlendi.');
clearTimeout(animationTimer);
};
}, []); // Sadece mount'ta ve unmount'ta temizlenir
return (
Ürün Listesi
useStateSearchTerm(e.target.value)}
placeholder="Ürün Ara..."
/>
{products.map(product => (
- {product.name}
))}
);
}
Bu revize edilmiş bileşende, her bir yan etki kendi useEffect Hook'u içinde izole edilmiştir. Bu, aşağıdaki avantajları sağlar:
- Daha İyi Okunabilirlik: Her
useEffect'in ne iş yaptığını anlamak daha kolaydır. - Daha Kolay Bakım: Bir yan etkide değişiklik yapmanız gerektiğinde, diğerlerini etkilemeden doğrudan ilgili
useEffectHook'una odaklanabilirsiniz. - Doğru Bağımlılık Yönetimi: Her
useEffectkendi özel bağımlılık dizisine sahiptir, bu da gereksiz çalıştırmaları engeller ve performans sorunlarını azaltır. - Daha Kolay Test Edilebilirlik: Her bir yan etkiyi ayrı ayrı test etmek daha basittir.
Vaka Analizi: Bir Kullanıcı Formu
Bir kullanıcı profil formunda, hem kullanıcının verilerini çekmeniz, hem form girişlerini izlemeniz, hem de belki de formun gönderilme durumuna göre bir geri bildirim göstermeniz gerekebilir. Bu senaryoda da yan etkileri ayırmak önemlidir:
- Kullanıcı verilerini çekme:
useEffect(() => { /* fetch user data */ }, [userId]); - Form doğrulama (inputlar değiştikçe):
useEffect(() => { /* validate form */ }, [formData]); - Form gönderme ve başarılı olduğunda bildirim gösterme:
useEffect(() => { /* show success message */ }, [isSubmittedSuccessfully]);
Bu şekilde, her bir useEffect kendi mantığını ve bağımlılıklarını izler, böylece bileşen daha öngörülebilir ve yönetilebilir hale gelir. React Hooks'un gücü, yan etkileri soyutlama ve ayrı ayrı yönetme yeteneğinde yatar, bu yüzden bu yeteneği tam olarak kullanmaktan çekinmeyin.
Referans Eşitliğini Göz Ardı Etmek: Yeniden Oluşturma ve Performans Sorunları
React, performansı optimize etmek için bileşenleri yalnızca state veya props değiştiğinde yeniden render eder. Ancak, bu "değişiklik" kavramı, nesneler ve fonksiyonlar söz konusu olduğunda referans eşitliği prensibine dayanır. Başlangıç seviyesi geliştiriciler, JavaScript'in nesnelerle ve fonksiyonlarla çalışma şeklindeki bu nüansı genellikle gözden kaçırır ve bu da gereksiz yeniden oluşturmalara (re-renders) ve ciddi performans düşüşlerine yol açabilir.
JavaScript'te, iki nesne veya fonksiyon, içerikleri aynı olsa bile, bellekte farklı yerlerde depolanıyorsa (yani referansları farklıysa) eşit kabul edilmezler. Her render döngüsünde, bir fonksiyonel bileşen yeniden çalışır ve bu sırada içinde tanımlanan her nesne ve fonksiyon yeniden oluşturulur, dolayısıyla yeni bir referansa sahip olur. Bu durum, özellikle bu nesne veya fonksiyonların alt bileşenlere prop olarak geçtiği veya useEffect bağımlılık dizisinde kullanıldığı durumlarda sorunlara neden olabilir.
Yaygın Hata: Inline Nesneler ve Fonksiyonlar
Bir prop olarak alt bileşene bir nesne veya fonksiyon geçirdiğinizde, bu alt bileşen React.memo ile optimize edilmiş olsa bile, üst bileşen her yeniden render olduğunda yeni bir referans alacağı için alt bileşen de gereksiz yere yeniden render olur:
import React, { useState, memo } from 'react';
// Alt Bileşen: Sadece 'props.onClick' değiştiğinde yeniden render olmalı
const Button = memo(({ onClick, label }) => {
console.log(${label} butonu render edildi);
return ;
});
function YanlisUstuBilesen() {
const [count, setCount] = useState(0);
// YANLIŞ: handleIncrement her render'da yeniden oluşturulur
const handleIncrement = () => {
setCount(prevCount => prevCount + 1);
};
// YANLIŞ: data objesi her render'da yeniden oluşturulur
const data = { value: count };
return (
Sayaç: {count}
Data değeri: {data.value}
);
}
Yukarıdaki örnekte, YanlisUstuBilesen her render olduğunda (örneğin "Sıfırla" butonuna basıldığında), handleIncrement fonksiyonu ve data nesnesi yeniden oluşturulur. Button bileşeni React.memo ile optimize edilmiş olsa bile, onClick prop'unun referansı her render'da değiştiği için Button bileşeni de her seferinde yeniden render olur. Bu küçük bir örnekte fark edilmeyebilir ancak büyük ve karmaşık uygulamalarda ciddi performans sorunlarına yol açabilir.
Benzer bir sorun, bir fonksiyon veya nesnenin useEffect bağımlılık dizisinde kullanılması durumunda da ortaya çıkar. Eğer bağımlılık dizisindeki bir nesne veya fonksiyon her render'da yeni bir referans alıyorsa, useEffect gereksiz yere tekrar çalışır, bu da performans kaybına veya beklenmedik yan etkilere neden olabilir.
Doğru Yaklaşım: useMemo ve useCallback ile Referans Eşitliğini Korumak
React, bu tür performans sorunlarını çözmek için iki özel Hook sunar: useCallback ve useMemo. Bu Hook'lar, pahalı hesaplamaları veya fonksiyon tanımlamalarını önbelleğe almanıza (memoize etmenize) olanak tanır, böylece yalnızca bağımlılıkları değiştiğinde yeniden oluşturulurlar.
useCallback(callback, dependencies): Bir fonksiyonu önbelleğe alır. Yalnızca bağımlılıkları değiştiğinde fonksiyonun yeni bir referansını döndürür. Alt bileşenlere prop olarak geçecek fonksiyonlar için idealdir.useMemo(callback, dependencies): Bir değerin (nesne, dizi, sayı vb.) sonucunu önbelleğe alır. Yalnızca bağımlılıkları değiştiğinde callback'i yeniden çalıştırır ve yeni değeri döndürür. Özellikle pahalı hesaplamaların sonucunu veya alt bileşenlere geçecek nesne prop'larını optimize etmek için kullanılır.
import React, { useState, memo, useCallback, useMemo } from 'react';
// Alt Bileşen: Sadece 'props.onClick' değiştiğinde yeniden render olmalı
const Button = memo(({ onClick, label }) => {
console.log(${label} butonu render edildi);
return ;
});
function DogruUstuBilesen() {
const [count, setCount] = useState(0);
// DOĞRU: useCallback ile handleIncrement fonksiyonunun referansını sabitle
const handleIncrement = useCallback(() => {
setCount(prevCount => prevCount + 1);
}, []); // Bağımlılık dizisi boş, çünkü setCount'un referansı sabit
// DOĞRU: useMemo ile data objesinin referansını sabitle
const data = useMemo(() => ({ value: count }), [count]);
return (
Sayaç: {count}
Data değeri: {data.value}
);
}
Bu doğru yaklaşımda, handleIncrement fonksiyonu useCallback ile sarılmıştır. Bağımlılık dizisi boş ([]) olduğu için, bu fonksiyon bileşenin yaşam döngüsü boyunca aynı referansa sahip olur. Benzer şekilde, data nesnesi useMemo ile sarılmıştır ve sadece count değeri değiştiğinde yeniden oluşturulur. Bu sayede, DogruUstuBilesen yeniden render olduğunda (örneğin "Sıfırla" butonu ile), Button bileşeni gereksiz yere yeniden render olmaz, çünkü onClick prop'unun referansı değişmez. Bu teknik, özellikle liste render'ları veya yoğun hesaplamalar içeren bileşenler için kritik öneme sahiptir.
Vaka Analizi: Büyük Bir Tablo Bileşeni
Büyük bir veri tablosu düşünün. Her satırda bir dizi işlem butonu (Düzenle, Sil vb.) bulunuyor. Eğer bu işlem butonlarının onClick handler'larını her satırda inline olarak veya useCallback kullanmadan tanımlarsanız, her tablonun yeniden render olmasında tüm satırlar ve butonlar gereksiz yere yeniden render olacaktır. useCallback kullanarak bu handler'ları optimize etmek, özellikle yüzlerce veya binlerce satır içeren tablolarda performansı gözle görülür şekilde artırabilir.
Kendi Hook'larınızı Yanlış Yere Mi Yazıyorsunuz? Özel Hook Kuralları
React Hooks'un en güçlü özelliklerinden biri, özel Hook'lar (Custom Hooks) oluşturma yeteneğidir. Özel Hook'lar, stateful mantığı fonksiyonel bileşenler arasında yeniden kullanılabilir bir şekilde paylaşmanıza olanak tanır. Ancak, Hooks'un belirli "Kuralları" vardır ve bu kuralları ihlal etmek, uygulamanızda beklenmedik hatalara, öngörülemez davranışlara ve hatta çalışma zamanı hatalarına yol açabilir. Başlangıç seviyesi geliştiriciler genellikle bu kuralları göz ardı ederler.
React'in iki temel Hook kuralı vardır:
- Sadece React Fonksiyonlarının En Üst Seviyesinde Çağırın: Hook'ları döngülerin, koşullu ifadelerin veya iç içe fonksiyonların içinde çağıramazsınız. Her zaman React fonksiyonel bileşeninin veya başka bir özel Hook'un en üst seviyesinde çağırmalısınız.
- Sadece React Fonksiyonlarından Çağırın: Hook'ları sadece fonksiyonel React bileşenlerinden veya özel Hook'lardan çağırabilirsiniz. Sıradan JavaScript fonksiyonlarından Hook çağıramazsınız.
Bu kurallar, React'in Hook'ların sırasını her render'da tutarlı bir şekilde takip etmesini ve doğru state'i doğru Hook'a atamasını sağlamak için kritik öneme sahiptir. React, Hook'ları bir dizi gibi yönetir; her render'da aynı sırayla çağrılmalarını bekler.
Yaygın Hata: Koşullu veya Döngü İçinde Hook Çağırmak
Bir state'i veya efekti belirli bir koşula bağlı olarak tanımlamak istediğinizde, Hook'u koşulun içine yerleştirmek büyük bir hatadır:
function YanlisKosulluHook() {
const [shouldShow, setShouldShow] = useState(false);
if (shouldShow) {
// YANLIŞ: Hook bir koşullu bloğun içinde çağrıldı
const [value, setValue] = useState(0);
useEffect(() => {
console.log("Değer:", value);
}, [value]);
}
// Başka bir örnek: Döngü içinde Hook
// const items = [1, 2, 3];
// items.forEach(() => {
// const [itemState, setItemState] = useState(false); // YANLIŞ!
// });
return (
{/* value ve setValue'a erişim burada mümkün olmayabilir */}
);
}
Bu örnekte, shouldShow değeri değiştiğinde, useState ve useEffect çağrılarının sırası değişir. React, bir sonraki render'da farklı sayıda veya sırayla Hook'larla karşılaştığında kafası karışır ve "Rendered fewer hooks than expected" gibi hatalar fırlatır. Bu kurallar, özellikle özel Hook'lar oluştururken daha da kritik hale gelir.
Doğru Yaklaşım: Koşulları Hook İçine Taşımak veya Hook'u Üstte Tutmak
Hook'ları her zaman fonksiyonel bileşenin veya özel Hook'un en üst seviyesinde çağırmalı, koşullu mantığı Hook'ların içine taşımalısınız. State'in veya yan etkinin kendisi koşullu olabilir, ancak Hook çağrısının kendisi koşullu olmamalıdır.
function DogruKosulluHook() {
const [shouldShow, setShouldShow] = useState(false);
// DOĞRU: Hook'lar en üst seviyede çağrılır
const [value, setValue] = useState(0);
useEffect(() => {
// DOĞRU: Koşullu mantık effect'in içinde yer alır
if (shouldShow) {
console.log("Değer:", value);
}
}, [shouldShow, value]);
const handleToggle = () => {
setShouldShow(!shouldShow);
// Belki de sadece gösterildiğinde değeri artırmak istersiniz
if (!shouldShow) { // Gizliden gösterilmeye geçerken
setValue(prev => prev + 1);
}
};
return (
{shouldShow && Gösterilen Değer: {value}
}
);
}
Bu doğru örnekte, useState(0) Hook'u her zaman en üst seviyede çağrılır. useEffect'in içindeki mantık ise shouldShow koşuluna bağlı olarak çalışır. Bu, React'in Hook'ların sırasını tutarlı bir şekilde takip etmesini sağlar ve hataları önler. Aynı zamanda, value state'ine bileşenin her yerinden erişilebilir olması da bir avantajdır.
Vaka Analizi: Form Alanları İçin Özel Hook Oluşturma
Bir formdaki her giriş alanı için state ve doğrulama mantığı yönettiğinizi düşünün. Yanlış bir yaklaşımla, her bir alan için döngü içinde useState çağırabilirsiniz. Ancak doğru yaklaşım, her bir form alanı için state ve mantığı soyutlayan, bu kurallara uyan bir özel Hook (örneğin, useFormField) oluşturmaktır:
// Doğru Özel Hook Örneği: useFormField
function useFormField(initialValue) {
const [value, setValue] = useState(initialValue);
const [error, setError] = useState(null);
const onChange = (e) => {
setValue(e.target.value);
// Doğrulama mantığı buraya gelebilir
if (e.target.value.length < 3) {
setError('Minimum 3 karakter olmalı.');
} else {
setError(null);
}
};
return { value, onChange, error };
}
function DogruForm() {
// Her alan için özel Hook çağrısı
const nameField = useFormField('');
const emailField = useFormField('');
const handleSubmit = (e) => {
e.preventDefault();
if (!nameField.error && !emailField.error) {
console.log('Form gönderildi:', { name: nameField.value, email: emailField.value });
} else {
console.log('Formda hatalar var.');
}
};
return (
);
}
Bu örnekte, useFormField özel Hook'u, her bir form alanı için state ve olay işleme mantığını kapsüller. DogruForm bileşeni içinde bu özel Hook'u her bir alan için ayrı ayrı çağırarak, Hooks kurallarına uymuş oluruz ve kodumuzu hem temiz hem de yeniden kullanılabilir hale getiririz.
React Hooks ile Ustalaşmak İçin Son İpuçları ve Sıkça Sorulan Sorular
React Hooks, React uygulamaları geliştirme şeklimizi dönüştürdü. İşlevsel bileşenlere güç katan ve kodumuzu daha modüler, yeniden kullanılabilir hale getiren bu yapı, doğru anlaşıldığında ve uygulandığında geliştirici verimliliğini önemli ölçüde artırır. Bu makale boyunca, başlangıç seviyesi geliştiricilerin en sık yaptığı beş yaygın hatayı inceledik: useState ile yanlış state güncellemeleri, useEffect bağımlılık dizisini doğru yönetmeme, yan etkileri tek bir useEffect'te birleştirme, referans eşitliğini göz ardı ederek gereksiz yeniden oluşturmalara yol açma ve Hooks kurallarını ihlal etme. Her bir hatayı kod örnekleri ve vaka analizleriyle destekleyerek, bu tuzaklardan nasıl kaçınabileceğinizi ve daha sağlam uygulamalar geliştirebileceğinizi gösterdik.
Unutmamak gerekir ki, React Hooks'a hakim olmak zaman ve pratik gerektirir. Temel prensipleri – özellikle React'in render döngüsü, immutability ve referans eşitliği kavramlarını – derinlemesine anlamak, Hooks'u etkin bir şekilde kullanmanın anahtarıdır. Hatalar yapmak öğrenme sürecinin doğal bir parçasıdır; önemli olan, bu hatalardan ders çıkararak ve en iyi uygulamaları benimseyerek sürekli gelişmektir. Kodunuzu sürekli olarak gözden geçirin, başkalarının kodlarını inceleyin ve topluluk kaynaklarından faydalanın. Stack Overflow, React belgeleri ve çeşitli bloglar, bilgi edinmek ve sorun gidermek için harika kaynaklardır. Özellikle React'in resmi belgelerindeki "Rules of Hooks" bölümünü dikkatlice okumanızı tavsiye ederim.
Ayrıca, React.StrictMode'u geliştirme ortamınızda kullanmak, Hooks ile ilgili potansiyel sorunları (örneğin, yan etkilerin çift çalışması gibi) erkenden yakalamanıza yardımcı olabilir. Bu mod, bileşenlerinizin beklenen davranışları sergilediğinden emin olmak için ek kontroller yapar. ESLint eklentisi eslint-plugin-react-hooks ise Hooks kurallarına uyduğunuzdan emin olmak için vazgeçilmez bir araçtır. Bu araçları kullanarak, hataları otomatik olarak tespit edebilir ve kod kalitenizi artırabilirsiniz.
Şimdi, React Hooks hakkında sıkça sorulan bazı sorulara göz atalım:
Sıkça Sorulan Sorular
-
React Hooks'u ne zaman kullanmalıyım?
React Hooks'u, React 16.8 ve üzeri sürümlerde yeni fonksiyonel bileşenler geliştirirken kullanmalısınız. State yönetimi, yaşam döngüsü metodları, context kullanımı veya diğer gelişmiş React özelliklerine ihtiyacınız olduğunda, sınıf bileşenleri yerine Hooks'u tercih etmek, daha modern, temiz ve yeniden kullanılabilir kod yazmanızı sağlar. Mevcut sınıf bileşenlerini tamamen Hooks'a geçirmek zorunda değilsiniz, ancak yeni geliştirme ve özellik eklemeleri için Hooks'u kullanmanız önerilir.
-
Class component'lardan Hooks'a geçmeli miyim?
Mevcut ve çalışan sınıf bileşenlerinizi hemen Hooks'a geçirmek zorunlu değildir. React, sınıf bileşenlerini desteklemeye devam edecektir. Ancak, yeni özellikler eklerken veya varolan sınıf bileşenlerinde önemli değişiklikler yaparken, bunları Hooks'a dönüştürmeyi düşünebilirsiniz. Bu geçiş, kod tabanınızın modernleşmesine, daha iyi test edilebilirliğe ve daha kolay bakıma olanak tanır. Geçiş yaparken küçük adımlarla ilerlemek ve her bileşeni ayrı ayrı dönüştürmek en iyi yaklaşımdır.
-
useRefveuseStatearasındaki fark nedir?useState, bileşenin yeniden render edilmesini tetikleyen ve UI'da görünen veriyi yönetmek için kullanılır. State değeri değiştiğinde bileşen yeniden render olur. Öte yandan,useRef, referans değerini bileşenin yaşam döngüsü boyunca korur ve değeri değiştiğinde bileşenin yeniden render edilmesini tetiklemez. Genellikle DOM elementlerine doğrudan erişmek, herhangi bir mutable değeri bileşen render'ları arasında tutmak (örneğin, bir zamanlayıcı kimliği) veya bir kapanış (closure) içinde değişen bir değeri yakalamak için kullanılır. Kısacası,useState"görsel" state için,useRefise "kalıcı" ve "görünmeyen" referanslar için kullanılır. -
useMemoveuseCallbackne zaman gereklidir?useMemoveuseCallbackperformans optimizasyonu araçlarıdır ve sadece ölçülebilir performans sorunları yaşadığınızda veya gereksiz yeniden render'ları önlemeniz gerektiğinde kullanılmalıdır.useCallback, bir fonksiyonun referansının aynı kalmasını sağlarken,useMemo, bir değerin (nesne, dizi veya pahalı bir hesaplama sonucu) referansının aynı kalmasını sağlar. Bu Hook'lar özellikle alt bileşenlere prop olarak nesne veya fonksiyon geçirdiğinizde ve bu alt bileşenlerinReact.memoile optimize edildiği durumlarda faydalıdır. Küçük uygulamalarda veya performans sorunu olmayan yerlerde aşırı kullanımları, Hook'ların kendilerinin getirdiği ek karmaşıklık nedeniyle ters etki yapabilir. -
React.StrictModeneden önemlidir?React.StrictMode, geliştirme ortamında potansiyel sorunları (örneğin, yan etkilere neden olan hatalı kod, eski API kullanımları veya beklenmeyen yaşam döngüsü davranışları) belirlemeye yardımcı olan bir araçtır. Uygulamanızda herhangi bir görsel değişiklik yapmaz, ancak bileşenleriniz içindeki belirli fonksiyonları iki kez çalıştırarak veya belirli uyarıları göstererek sizi olası problemlere karşı uyarır. Örneğin,useEffectiçindeki temizleme fonksiyonlarının doğru çalıştığından emin olmak için effect'lerinizi iki kez tetikleyebilir. Bu, hataları erkenden yakalamak ve daha sağlam bir uygulama geliştirmek için kritik öneme sahiptir.
