React’te useEffect Hook’u ile Web API Çağrıları Nasıl Yapılır?
React, modern web uygulamaları geliştirmek için popüler bir JavaScript kütüphanesidir. Bileşen tabanlı mimarisi sayesinde kullanıcı arayüzlerini küçük, yeniden kullanılabilir parçalara ayırmayı ve yönetmeyi kolaylaştırır. Ancak, çoğu dinamik web uygulamasının harici veri kaynaklarına, yani Web API’lerine bağlanması gerekir. Kullanıcıların göreceği dinamik içerik, genellikle bir sunucudan veya üçüncü taraf bir hizmetten çekilir. Bu veri çekme (data fetching) işlemleri, React bileşenlerinin yaşam döngüsünde belirli anlarda gerçekleşmesi gereken “yan etkiler” (side effects) kategorisine girer.
React’in sınıf tabanlı bileşenlerinde bu tür yan etkiler componentDidMount, componentDidUpdate ve componentWillUnmount gibi yaşam döngüsü metotları kullanılarak yönetilirdi. Ancak React Hooks’un tanıtılmasıyla birlikte, fonksiyonel bileşenlerde de bu yetenekler useEffect hook’u aracılığıyla sunuldu. useEffect, fonksiyonel bileşenlerde veri çekme, abonelikler kurma veya DOM’u doğrudan manipüle etme gibi yan etkileri gerçekleştirmenin standart yoludur.
Bu makalede, React’te useEffect hook’unu kullanarak Web API çağrılarını nasıl yapacağımızı detaylı bir şekilde inceleyeceğiz. Temel API çağrısı yapısından başlayarak, veri durumunu yönetmeye, bağımlılık dizisini doğru kullanmaya, hata yönetimine, temizleme fonksiyonlarına ve hatta daha gelişmiş senaryolara kadar adım adım ilerleyeceğiz. Amacımız, useEffect ile sağlam, verimli ve hata toleranslı API çağrıları yapma konusunda size kapsamlı bir rehber sunmaktır.
useEffect Hook’una Giriş
useEffect hook’u, React fonksiyonel bileşenlerinde “yan etkileri” gerçekleştirmek için kullanılır. Yan etkiler, bir fonksiyonun ana işlevinin dışında kalan ve dış dünya ile etkileşime giren her türlü eylemi ifade eder. Bir React bileşeni bağlamında, bu genellikle şunları içerir:
* Veri Çekme (Data Fetching): Bir API’den veri almak.
* Abonelikler (Subscriptions): WebSocket gibi harici bir veri kaynağına abone olmak.
* DOM Manipülasyonu: Bileşenin dışında kalan DOM elemanlarını doğrudan değiştirmek (örneğin, belge başlığını güncellemek).
* Zamanlayıcılar (Timers): setTimeout veya setInterval kullanmak.
useEffect‘in temel yapısı şu şekildedir:
useEffect(callback, [dependencies]);
* callback (Geri Çağırma Fonksiyonu): Bu, yan etki mantığınızı içeren fonksiyondur. React, bileşen her render edildiğinde (veya bağımlılıklar değiştiğinde) bu fonksiyonu çalıştırır.
* [dependencies] (Bağımlılık Dizisi): Bu isteğe bağlı bir dizidir ve useEffect‘in ne zaman yeniden çalıştırılacağını kontrol eder.
* Boş dizi ([]): useEffect yalnızca bileşen ilk kez monte edildiğinde (mount) çalışır ve bir daha çalışmaz (sınıf bileşenlerindeki componentDidMount gibidir). Bu, genellikle ilk veri çekme işlemleri için idealdir.
* Hiçbir bağımlılık dizisi yok: useEffect bileşen her render edildiğinde çalışır. Bu, genellikle bir sonsuz döngüye yol açabileceği için dikkatli kullanılmalıdır ve çoğu zaman istenmeyen bir durumdur.
* Değerler içeren bir dizi ([propA, stateB]): useEffect sadece dizideki değerlerden herhangi biri bir önceki render’dan farklıysa çalışır. Bu, belirli prop veya state değişikliklerine tepki vermek için kullanılır.
useEffect‘in dönüş değeri olarak bir fonksiyon döndürülebilir. Bu dönüş fonksiyonu, bileşen kaldırıldığında (unmount) veya useEffect bir sonraki çalıştığında (bağımlılıklar değiştiği için) temizleme (cleanup) işlemi yapmak için kullanılır. Örneğin, abonelikleri iptal etmek veya zamanlayıcıları temizlemek için idealdir. Veri çekme senaryolarında, devam eden bir isteği iptal etmek için kullanılabilir.
Temel API Çağrısı Yapısı
Web API’lerinden veri çekmek için JavaScript’in yerleşik fetch API’sini veya üçüncü taraf kütüphaneleri (örneğin, Axios) kullanabiliriz. Bu makalede fetch API’sine odaklanacağız, çünkü herhangi bir ek kurulum gerektirmez ve modern tarayıcılar tarafından geniş çapta desteklenir.
fetch API’si, Promise tabanlıdır ve asenkron işlemler için async/await sözdizimi ile kullanımı çok daha okunabilir hale gelir. Temel bir fetch isteği şu şekilde görünür:
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error(HTTP error! status: ${response.status});
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error("Veri çekme hatası:", error);
}
}
fetchData();
Bu yapıyı useEffect hook’u içine entegre ederken, genellikle bileşenimizin durumu (state) ile etkileşime girmesi gerekir. useState hook’u, bileşenin durumunu yönetmek için kullanılır ve API’den gelen verileri, yükleme durumunu ve hata mesajlarını saklamak için vazgeçilmezdir.
Aşağıda, useEffect ve useState kullanarak basit bir veri çekme örneği verilmiştir:
import React, { useState, useEffect } from 'react';
function MyComponent() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchData() {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
if (!response.ok) {
throw new Error(HTTP hatası! Durum: ${response.status});
}
const result = await response.json();
setData(result);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
}
fetchData(); // useEffect içinde tanımlanan asenkron fonksiyonu çağır
}, []); // Boş bağımlılık dizisi: Sadece bileşen ilk yüklendiğinde çalışır.
if (loading) return Yükleniyor...;
if (error) return Hata: {error.message};
return (
API'den Gelen Veri:
Başlık: {data.title}
İçerik: {data.body}
);
}
export default MyComponent;
Bu örnekte:
1. useState kullanarak data, loading ve error durum değişkenlerini tanımladık. loading başlangıçta true olarak ayarlanmıştır çünkü veri çekme işlemi hemen başlayacaktır.
2. useEffect içinde fetchData adında asenkron bir fonksiyon tanımladık. Bu fonksiyon, API’den veri çeker, başarılı olursa setData ile durumu günceller, bir hata olursa setError ile hatayı kaydeder ve her durumda setLoading(false) ile yükleme durumunu sonlandırır.
3. fetchData() çağrısı, useEffect içinde tanımlandıktan hemen sonra yapılır.
4. Bağımlılık dizisi [] olduğu için, bu useEffect sadece bileşen ilk monte edildiğinde bir kez çalışır.
Veri Durumunu Yönetme
API çağrıları yaparken, verinin kendisi kadar, bu verinin yükleme durumu ve olası hataları da kullanıcıya doğru bir şekilde yansıtılmalıdır. useState hook’u, bu farklı durumları yönetmek için temel araçtır. Genellikle üç ana durum değişkeni kullanılır:
1. data: API’den başarılı bir şekilde çekilen veriyi saklar. Başlangıç değeri null veya boş bir dizi/obje olabilir, beklenen veri tipine göre değişir.
2. isLoading (veya loading): Veri çekme işleminin devam edip etmediğini gösteren bir boolean bayrağıdır. true ise yükleme sürüyor demektir, false ise işlem tamamlanmıştır. Kullanıcıya bir yükleme göstergesi (spinner, iskelet ekran) göstermek için kullanılır.
3. error: API çağrısı sırasında meydana gelen herhangi bir hatayı saklar. Başlangıç değeri null olabilir. Bir hata oluştuğunda, bu değişken hata nesnesini veya mesajını içerir ve kullanıcıya hata mesajını göstermek için kullanılır.
Bu durum değişkenlerinin API çağrısı yaşam döngüsündeki akışı şöyledir:
1. Çağrı Başlamadan Önce:
* setLoading(true)
* setError(null) (önceki hataları temizlemek için)
2. Çağrı Başarılı Olduğunda:
* setData(gelenVeri)
* setLoading(false)
3. Çağrı Başarısız Olduğunda:
* setError(hataObjesi)
* setLoading(false)
4. finally Bloğunda: setLoading(false) genellikle try...catch bloğunun finally kısmında çağrılır, böylece hem başarılı hem de başarısız durumlarda yükleme durumu doğru bir şekilde kapanır.
Yukarıdaki örnekte bu akışı zaten görmüştük. Bu durumları kullanarak bileşen içinde koşullu renderlama yapmak, kullanıcı deneyimini önemli ölçüde artırır:
// ... (önceki import ve useState tanımlamaları)
function MyComponentWithStateManagement() {
const [posts, setPosts] = useState([]); // Bir dizi beklediğimiz için başlangıç değeri boş dizi
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchPosts = async () => {
setLoading(true); // Yükleme başladı
setError(null); // Önceki hataları temizle
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
if (!response.ok) {
throw new Error(HTTP hatası! Durum: ${response.status});
}
const data = await response.json();
setPosts(data); // Veri başarıyla alındı
} catch (err) {
setError(err); // Hata oluştu
} finally {
setLoading(false); // Yükleme tamamlandı (başarılı veya başarısız)
}
};
fetchPosts();
}, []);
if (loading) {
return
Gönderiler yükleniyor...
{/ Buraya bir spinner veya iskelet ekran eklenebilir /}
;
}
if (error) {
return
Veri çekilirken bir hata oluştu: {error.message}
;
}
return (
{posts.map(post => (
{post.title}
{post.body}
))}
);
}
export default MyComponentWithStateManagement;
Bu örnek, kullanıcıya akıcı bir deneyim sunmak için yükleme ve hata durumlarının nasıl yönetildiğini daha net gösterir.
Bağımlılık Dizisi ve Kontrollü Yeniden Çalışma
useEffect hook’unun ikinci argümanı olan bağımlılık dizisi, performans ve doğru çalışma açısından kritik öneme sahiptir. Bu dizi, useEffect içindeki yan etkinin ne zaman yeniden çalıştırılacağını belirler.
1. Boş Bağımlılık Dizisi ([])
useEffect‘in sadece bileşen ilk monte edildiğinde çalışmasını sağlar. Bu, sınıf bileşenlerindeki componentDidMount metoduna karşılık gelir. Genellikle bir bileşenin yaşam döngüsünde yalnızca bir kez yapılması gereken veri çekme işlemleri için kullanılır.
useEffect(() => {
// Bu kod sadece bileşen ilk kez DOM'a eklendiğinde çalışır.
// Örnek: İlk sayfa yüklendiğinde bir listeyi çekmek.
fetch('https://api.example.com/initial-data').then(...);
}, []); // Boş dizi
2. Bağımlılık Dizisi Yok
Eğer ikinci argüman olarak bağımlılık dizisi belirtilmezse, useEffect bileşen her render edildiğinde çalışır. Bu, genellikle istenmeyen bir durumdur ve sonsuz döngülere yol açabilir, özellikle useState güncellemeleri içeriyorsa.
useEffect(() => {
// DİKKAT: Bu kod her render'da çalışır!
// Eğer bu içinde bir state güncelleniyorsa, sonsuz döngüye girebilirsiniz.
console.log('Her renderda çalışıyorum!');
}); // Bağımlılık dizisi yok
3. Değerler İçeren Bağımlılık Dizisi ([value1, value2])
useEffect içindeki yan etkinin, dizideki değerlerden herhangi biri bir önceki render’dan farklıysa yeniden çalışmasını sağlar. Bu, belirli prop veya state değişikliklerine tepki vermek için idealdir. Örneğin, bir kullanıcının ID’si değiştiğinde o kullanıcının verilerini çekmek gibi.
import React, { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchUser = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(https://jsonplaceholder.typicode.com/users/${userId});
if (!response.ok) {
throw new Error(Kullanıcı bulunamadı! Durum: ${response.status});
}
const data = await response.json();
setUser(data);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
if (userId) { // userId'nin geçerli olduğundan emin ol
fetchUser();
} else {
setUser(null);
setLoading(false);
}
}, [userId]); // userId değiştiğinde useEffect yeniden çalışır
if (loading) return Kullanıcı bilgileri yükleniyor...;
if (error) return Hata: {error.message};
if (!user) return Lütfen bir kullanıcı seçin.;
return (
Kullanıcı Profili: {user.name}
E-posta: {user.email}
Şehir: {user.address.city}
);
}
export default UserProfile;
Bu örnekte, userId prop’u useEffect‘in bağımlılık dizisine dahil edilmiştir. Bu sayede, UserProfile bileşenine farklı bir userId prop’u geldiğinde (örneğin bir üst bileşenden), useEffect yeniden çalışacak ve yeni kullanıcı için veri çekecektir.
Önemli Not: Bağımlılık dizisine eklenen her değişken, useEffect‘in doğru çalışması için stabil olmalıdır. Eğer diziye bir obje veya fonksiyon gibi referans tipli bir değişken ekler ve bu değişken her render’da yeniden oluşturulursa (örneğin, doğrudan useEffect içinde bir obje oluşturup bağımlılık olarak vermek), useEffect her zaman bu değişkenin değiştiğini düşünecek ve sonsuz bir döngüye girecektir. Bu tür durumlar için useCallback veya useMemo hook’ları kullanılabilir, ancak çoğu basit API çağrısı senaryosunda bu kadar ileri gitmeye gerek kalmaz.
Hata Yönetimi
API çağrılarında hata yönetimi, uygulamanın sağlamlığı ve kullanıcı deneyimi için kritik öneme sahiptir. Ağ hataları, sunucu hataları (4xx, 5xx), veya API’den gelen geçersiz yanıtlar gibi çeşitli sorunlarla karşılaşılabilir.
fetch API’si, ağ hataları (örneğin, internet bağlantısı yok) durumunda bir Promise’i reddeder, ancak HTTP durum kodları (örneğin, 404 Not Found veya 500 Internal Server Error) durumunda Promise’i reddetmez; bunun yerine response.ok özelliğini false olarak ayarlar. Bu nedenle, hem ağ hatalarını hem de HTTP durum hatalarını ayrı ayrı ele almak önemlidir.
// ... (önceki import ve useState tanımlamaları)
function ErrorHandlingComponent() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch('https://jsonplaceholder.typicode.com/nonexistent-endpoint'); // Bilerek hatalı bir URL
// HTTP durum kodunu kontrol et
if (!response.ok) {
// Hata durumunda response.json() ile sunucudan gelen hata mesajını almaya çalışabiliriz
const errorData = await response.json().catch(() => ({ message: 'Bilinmeyen sunucu hatası.' }));
throw new Error(HTTP hatası! Durum: ${response.status} - ${errorData.message || response.statusText});
}
const result = await response.json();
setData(result);
} catch (err) {
// Ağ hataları veya yukarıdaki throw new Error yakalanır
console.error("Veri çekme sırasında bir hata oluştu:", err);
setError(err);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
if (loading) return Yükleniyor...;
if (error) return Hata: {error.message};
return (
API'den Gelen Veri:
{data ? {data.title}
: Veri bulunamadı.
}
);
}
export default ErrorHandlingComponent;
Bu örnekte:
* try...catch bloğu, hem fetch Promise’inin reddedilmesi durumunda (ağ hatası) hem de bizim manuel olarak throw new Error ile fırlattığımız durumlarda (HTTP durum hatası) hataları yakalar.
* response.ok kontrolü, 200-299 aralığında olmayan HTTP durum kodlarını yakalamak için kullanılır.
* response.json().catch(() => ...) ile, sunucudan gelen hata mesajını (eğer varsa) almaya çalışırız. Eğer JSON parse edilemezse, varsayılan bir hata mesajı kullanılır.
* setError(err) ile hata durumunu güncelleyip kullanıcıya gösteririz.
Temizleme Fonksiyonları
useEffect hook’u, bileşen kaldırıldığında (unmount) veya bağımlılıklar değiştiğinde önceki yan etkinin temizlenmesini sağlayan bir fonksiyon döndürebilir. Bu temizleme fonksiyonu, özellikle abonelikleri iptal etmek, zamanlayıcıları durdurmak veya devam eden ağ isteklerini iptal etmek gibi durumlarda bellek sızıntılarını ve istenmeyen davranışları önlemek için çok önemlidir.
API çağrıları söz konusu olduğunda, bir bileşen kaldırılmadan önce devam eden bir fetch isteğini iptal etmek, “Can’t perform a React state update on an unmounted component” (Kaldırılmış bir bileşende React durum güncellemesi yapılamaz) uyarısını önlemenin yaygın bir yoludur. fetch API’si doğrudan bir iptal mekanizmasına sahip olmasa da, AbortController API’si bu işlevi sağlar.
import React, { useState, useEffect } from 'react';
function AbortableFetchComponent() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const abortController = new AbortController(); // Yeni bir AbortController oluştur
const signal = abortController.signal; // İptal sinyalini al
const fetchData = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts/1', { signal }); // İstekle birlikte sinyali gönder
if (!response.ok) {
throw new Error(HTTP hatası! Durum: ${response.status});
}
const result = await response.json();
setData(result);
} catch (err) {
// Eğer hata AbortError ise, isteğin iptal edildiği anlamına gelir.
// Bu durumda state güncellemesi yapmayız.
if (err.name === 'AbortError') {
console.log('Fetch isteği iptal edildi.');
} else {
setError(err);
}
} finally {
setLoading(false);
}
};
fetchData();
// Temizleme fonksiyonu: Bileşen kaldırıldığında veya bağımlılıklar değiştiğinde çalışır.
return () => {
abortController.abort(); // Devam eden isteği iptal et
};
}, []); // Bağımlılık dizisi boş
if (loading) return Yükleniyor...;
if (error) return Hata: {error.message}