Menu

React'te useEffect ile Veri Çekme: Yükleme, Hata, Yarış

React'te veriyi isteği useEffect içinde başlatarak, yükleme, hata ve veriyi state'te tutarak ve girdiler değiştikten sonra gelen yanıtları yok sayarak çek. Efektlerde async/await, AbortController, yarış durumları ve ne zaman bunun yerine bir kütüphane kullanılacağı anlatılıyor.

Bu sayfada çalıştırılabilir editörler var - düzenle, çalıştır ve sonucu anında gör.

React'te veri çekmek için isteği useEffect içinde başlat, yanıtı state'e kaydet ve gelene kadar bir yükleme mesajı render et. İsteğin bağlı olduğu değerleri, örneğin bir id'yi, bağımlılık dizisine koy; böylece onlar değiştiğinde efekt yeniden veri çeker.

Bu sayfadaki örnekler ağa erişemez, bu yüzden fetchUser sahte bir API'dir: fetch'in yaptığı gibi bir gecikmeden sonra resolve olan bir promise. Yükleme metninin daha uzun kaldığını görmek için 800'ü 3000 yap.

Gerçek bir API ile efekt gövdesi şöyle görünür:

useEffect(() => {
    fetch('https://api.example.com/users/1')
        .then((res) => res.json())
        .then((data) => setUser(data));
}, []);

Yükleme, hata ve veri

Bir isteğin ekranın göstermesi gereken üç sonucu vardır: hâlâ yükleniyor, başarısız oldu ya da bitti. Her birini state'te tut ve efekt yeni bir istek başlattığında onları sıfırla.

User 3'e tıkla: sahte API reddeder, hata mesajı adın yerini alır ve konsol hatayı gösterir. finally bloğu loading'i iki yolda da temizler, böylece başarısız bir istek göstergeyi asla açık bırakmaz.

fetch gerçek bir sunucuyla konuştuğunda, 404 ya da 500 yanıtı promise'i reddetmez. res.ok'u kontrol et ve kendin hata fırlat:

const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();

Bir efektin içinde async/await

useEffect'e aktardığın fonksiyon async olamaz. React onun hiçbir şey ya da bir temizleme fonksiyonu döndürmesini bekler, async bir fonksiyon ise her zaman bir promise döndürür.

// Wrong: the effect returns a promise
useEffect(async () => {
    const data = await fetchUser(id);
    setUser(data);
}, [id]);

// Right: define an async function inside and call it
useEffect(() => {
    async function load() {
        const data = await fetchUser(id);
        setUser(data);
    }
    load();
}, [id]);

Yarış durumları

Id hızla değiştiğinde aynı anda iki istek yoldadır. Sırayla yanıt vereceklerinin hiçbir garantisi yoktur. Eskisi daha yavaşsa en son gelir ve kullanıcının en son seçtiği id'nin verisini ezer.

Çözüm bir ignore bayrağıdır. Efektin her çalışmasının kendi bayrağı vardır ve temizlemesi onu true yapar. React temizlemeyi bir sonraki çalışmadan önce çalıştırdığı için, eskimiş bir çalışmadan gelen yanıt ignore === true görür ve atılır. Bu blok aynı profili iki kez, bayraksız ve bayraklı olarak render eder:

Düğmeye tıkla ve bekle. User 2'nin hızlı yanıtı önce gelir, bu yüzden iki profil de kayıt yazar ve Grace'i tutar. Tıklamadan yaklaşık bir buçuk saniye sonra user 1'in yavaş yanıtı gelir: bayraksız profil, user 2 seçili olmasına rağmen Ada'ya geçer; bayraklı olan ise dropped stale Ada yazar ve Grace'i korur.

Aynı hata arama kutularında, sekmelerde ve sunucu tarafından filtrelenen her listede görünür. Yukarıdaki yükleme örneği kısa kalmak için bayrağı atlıyor, ama bir efektteki her veri çekme işleminde bu bayrak ya da aşağıdaki iptal olmalıdır.

AbortController ile iptal etmek

ignore bayrağı eskimiş bir yanıtı atar, ama istek yine de sonuna kadar çalışır. Gerçek fetch ile onu iptal edebilirsin. Efektte bir AbortController oluştur, signal'ini fetch'e aktar ve temizlemede abort() çağır:

useEffect(() => {
    const controller = new AbortController();

    async function load() {
        try {
            const res = await fetch(`/api/users/${id}`, { signal: controller.signal });
            if (!res.ok) throw new Error(`HTTP ${res.status}`);
            setUser(await res.json());
        } catch (err) {
            if (err.name === 'AbortError') return; // cancelled on purpose
            setError(err);
        }
    }

    load();
    return () => controller.abort();
}, [id]);

İptal edilmiş bir fetch bir AbortError ile reddedilir, bu yüzden catch bloğu onu bir başarısızlık olarak göstermek yerine atlar. İptal unmount'u da kapsar: bileşen kaldırıldığında temizlemesi isteği iptal eder.

Bunu özel bir hook'a taşımak

Yükleme, hata ve yarış koruması veri çeken her bileşende aynıdır. Onları bir özel hook'a koy; her bileşen veriyi tek satırda ister.

fetchPosts bileşenin dışında tanımlanmıştır, bu yüzden kimliği hiç değişmez ve bağımlılık dizisinde güvenlidir. App içinde tanımlansaydı her render'da yeni bir fonksiyon olurdu, efekt her render'dan sonra çalışırdı ve efekt state ayarladığı için veri çekmeyi hiç bırakmazdı.

500'ü 2000 yap ve düğmeye art arda iki kez tıkla: konsol iki istek gösterir ve yalnızca sonunda kaldığın konunun gönderileri görünür.

Bir olay işleyicisinde veri çekmek

Bir efekt, bileşenin ekranda olduğu için ihtiyaç duyduğu veri içindir: bir profil sayfası profilini yükler. Bir istek kullanıcı bir şey yaptığı için oluyorsa, örneğin Search ya da Save'e bastığında, onu olay işleyicisinde yap. Onu neyin tetiklediğini tam olarak bilirsin ve ilgisiz state değiştiğinde hiçbir şey yeniden çalışmaz.

async function handleSubmit(e) {
    e.preventDefault();
    setStatus('saving');
    const res = await fetch('/api/notes', { method: 'POST', body: JSON.stringify({ text }) });
    setStatus(res.ok ? 'saved' : 'error');
}

Yaygın hatalar

Eksik bağımlılıklar. id'yi okuyan ama [] içeren bir efekt sonsuza kadar ilk kullanıcıyı çeker. İsteğin kullandığı her değeri listele.

Bileşen gövdesinde veri çekmek. Bir efektin dışındaki fetch her render'da çalışır ve state ayarlarsa bir döngü başlatır.

fetch'in hatalarda reddedeceğine güvenmek. Yalnızca ağ başarısız olduğunda reddeder. res.ok'u kontrol et.

Yükleme sıfırlamasını unutmak. Id değiştiğinde loading'i yeniden true yap, yoksa eski veri yeni verinin yolda olduğuna dair hiçbir işaret olmadan ekranda kalır.

Ne zaman bir kütüphane ya da framework kullanılmalı

Bir efektte veri çekmek birkaç istek için iyidir. Önbelleğe almaz: aynı profili iki kez aç, iki kez çeker. Bileşenler arasında veri paylaşmaz, başarısızlıkta yeniden denemez ya da sekme yeniden odak aldığında yeniden çekmez. Kütüphaneler bunu senin yerine yapar:

import { useQuery } from '@tanstack/react-query';

function Profile({ id }) {
    const { data, error, isPending } = useQuery({
        queryKey: ['user', id],
        queryFn: () => fetch(`/api/users/${id}`).then((res) => res.json()),
    });

    if (isPending) return <p>Loading...</p>;
    if (error) return <p>{error.message}</p>;
    return <p>{data.name}</p>;
}

TanStack Query ve SWR istemci tarafı veri için yaygın seçeneklerdir. Framework'ler daha da ileri gider ve veriyi sayfa tarayıcıya ulaşmadan önce sunucuda yükler: sunucu bileşenleriyle Next.js, loader'larla React Router. Bu, yükleme parlamasını ve bir ebeveynin verisi gelmeden bir çocuğun veri çekmeye bile başlayamadığı durumda başlayan istek zincirini önler.

React 19: Suspense ile use()

React 19, render sırasında bir promise okuyan use'u ekler. Bileşen promise resolve olana kadar askıya alınır ve en yakın <Suspense> sınırı bu arada bir yedek içerik gösterir, böylece bileşenin kendisinin bir yükleme state'i olmaz:

import { use, Suspense } from 'react';

function Profile({ userPromise }) {
    const user = use(userPromise);
    return <p>{user.name}</p>;
}

<Suspense fallback={<p>Loading...</p>}>
    <Profile userPromise={userPromise} />
</Suspense>

Promise, onu okuyan render'ın içinde değil, bileşenin dışında (bir framework, bir önbellek ya da bir ebeveyn tarafından) oluşturulmalıdır; yoksa her render yeni bir istek başlatır. use hook'u sayfası bunu çalıştırılabilir örneklerle anlatıyor.

Sıkça Sorulan Sorular

Bir React bileşeni yüklendiğinde veri nasıl çekilir?

İsteği, bağlı olduğu değerler bağımlılık dizisinde olacak şekilde (yoksa []) bir useEffect içinde başlat ve sonucu useState ile state'te sakla. Veri gelene kadar bir yükleme mesajı render et.

useEffect callback'i neden async olamaz?

Async bir fonksiyon her zaman bir promise döndürür, React ise efektin ya hiçbir şey ya da bir temizleme fonksiyonu döndürmesini bekler. Efektin içinde async bir fonksiyon yaz ve onu hemen çağır.

React veri çekmede yarış durumu (race condition) nedir?

Girdi hızla değiştiğinde iki istek aynı anda yoldadır ve eskisi en son yanıt verip yeni veriyi ezebilir. Efektin temizlemesinde bir ignore bayrağı ayarla ve bayrak ayarlıyken setState'i atla, ya da isteği bir AbortController ile iptal et.

Veri çekmek için useEffect mi, yoksa bir kütüphane mi kullanmalıyım?

useEffect küçük uygulamalar için çalışır ve anlamaya değer. Önbellekleme, tekrar eden istekleri birleştirme, yeniden deneme ve yeniden çekme için TanStack Query ya da SWR gibi bir kütüphane veya Next.js gibi bir framework'ün yerleşik veri yüklemesi, bu mantığı kendin yazmaktan seni kurtarır.

Veri yüklenirken nasıl bir yükleme göstergesi gösterilir?

State'te bir loading boolean'ı (ya da bir status metni) tut, istek başlamadan önce ayarla ve istek bittiğinde hem başarı hem hata yolunda temizle. Değer true iken göstergeyi render et.

Coddy programlama dilleri çizimi

Coddy ile kodlamayı öğren

BAŞLA