<Suspense> alt elemanları yüklenene kadar bir alternatif (fallback) göstermenize olanak sağlar.

<Suspense fallback={<Loading />}>
<SomeComponent />
</Suspense>

Referans

<Suspense>

Prop’lar

  • children: Render etmek istediğiniz asıl kullanıcı arayüzüdür. Eğer children render edilirken askıya alınırsa, Suspense sınırı fallback’i render etmeye geçer.
  • fallback: Eğer asıl kullanıcı arayüzünün yüklemesi tamamlanmamışsa, onun yerine render edilecek alternatif bir kullanıcı arayüzüdür. Herhangi geçerli React düğümü kabul edilir, ancak pratikte, bir fallback hafif bir yer tutucu görünümdür, örneğin bir yükleniyor göstergesi ya da iskelet. Suspense, children askıya alındığında otomatik olarak fallback’e geçer ve veri hazır olduğunda children’a geri döner. Eğer fallback render edilirken askıya alınırsa, en yakın üst Suspense sınırını etkinleştirir.

Uyarılar

  • React ilk kez yüklenemeden önce askıya alınan renderlar için herhangi bir state saklamaz. Bileşen yüklendikten sonra, React askıya alınmış ağacı sıfırdan yeniden render etmeye çalışacaktır.
  • Eğer suspense ağaç için içerik gösteriyorduysa, ama sonrasında tekrar askıya alındıysa, askıya alınmayı tetikleyen güncelleme startTransition veya useDeferredValue tarafından tetiklenmediyse, fallback tekrar gösterilecektir.
  • Eğer React halihazırda gösterilen bir içeriği tekrar askıya alındığı için gizlemek zorunda kalırsa, içerik ağacındaki layout Effect’lerini temizleyecektir. İçerik tekrar gösterilmeye hazır olduğunda, React layout Effect’leri tekrar tetikleyecektir. Bu, DOM layout’unu ölçen Effect’lerin içerik gizliyken bunu yapmaya çalışmamasını sağlar.
  • React includes under-the-hood optimizations like Streaming Server Rendering and Selective Hydration that are integrated with Suspense. Read an architectural overview and watch a technical talk to learn more.
  • React Server Render’ını Stream etme ve Selektif Hydrate Etme gibi Suspense ile entegre olan altta yatan optimizasyonlar içerir. Daha fazla bilgi almak için mimari bir bakışı okuyun ve teknik bir konuşmayı izleyin.

Kullanım

İçerik yüklenirken bir fallback gösterme

Uygulamanızın herhangi bir parçasını bir Suspense sınırıyla sarabilirsiniz:

<Suspense fallback={<Loading />}>
<Albums />
</Suspense>

React yükleniyor fallback’inizi alt elemanların ihtiyaç duyduğu tüm kod ve veriler yüklenene kadar gösterecektir.

Aşağıdaki örnekte, Albums bileşeni albümler listesini fetch ederken askıya alınır. Render etmeye hazır olana kadar, React fallback’i —sizin Loading bileşeniniz— göstermek için en yakın Suspense sınırını etkinleştirir. Sonra, veri yüklendiğinde, React Loading fallback’ini gizler ve Albums bileşenini verilerle render eder.

import { Suspense } from 'react';
import Albums from './Albums.js';

export default function ArtistPage({ artist }) {
  return (
    <>
      <h1>{artist.name}</h1>
      <Suspense fallback={<Loading />}>
        <Albums artistId={artist.id} />
      </Suspense>
    </>
  );
}

function Loading() {
  return <h2>🌀 Yükleniyor...</h2>;
}

Not

Sadece Suspense özellikli veri kaynakları Suspense bileşenini aktive edecektir. Bunlara örnek olarak:

  • Relay ve Next.js gibi Suspense özellikli framework’lerle veri fetch etme.
  • lazy ile bileşen kodunu tembel yükleme (lazy-loading).
  • use ile bir Promise’in değerini okuma.

Suspense, veri bir effect ya da olay yöneticisi içinde fetch edildiğinde tespit etmez.

Yukarıdaki Albums bileşeninin içinde veri yüklemek için kullanacağınız tam yol framework’ünüze bağlıdır. Eğer Suspense özellikli bir framework kullanıyorsanız, detayları framwork’ün veri fetch etme dokümantasyonunda bulabilirsiniz.

Kanaat sahibi bir framework olmadan Suspense özellikli veri fetch etme henüz desteklenmiyor. Suspense özellikli bir veri kaynağı implement etmenin gereksinimleri henüz düzensiz ve belgelenmemiş durumda. Veri kaynaklarını Suspense ile entegre etmek için resmi bir API, React’in gelecek sürümlerinde yayınlanacaktır.


İçeriği tek seferde birlikte gösterme

Varsayılan olarak, Suspense içindeki tüm ağaç tek bir birim olarak ele alınır. Örneğin, eğer bu bileşenlerden sadece biri veri beklemek için askıya alınırsa, tümü birlikte yükleniyor göstergesiyle değiştirilecektir:

<Suspense fallback={<Loading />}>
<Biography />
<Panel>
<Albums />
</Panel>
</Suspense>

Sonrasında, hepsi görüntülenmeye hazır olduğunda, hepsi birlikte tek seferde açığa çıkacaktır.

Aşağıdaki örnekte, hem Biography hem Albums veri fetch etmekte. Ancak, tek bir Suspense sınırı altında gruplandıkları için, bu bileşenler her zaman aynı anda “açığa çıkıyor”.

import { Suspense } from 'react';
import Albums from './Albums.js';
import Biography from './Biography.js';
import Panel from './Panel.js';

export default function ArtistPage({ artist }) {
  return (
    <>
      <h1>{artist.name}</h1>
      <Suspense fallback={<Loading />}>
        <Biography artistId={artist.id} />
        <Panel>
          <Albums artistId={artist.id} />
        </Panel>
      </Suspense>
    </>
  );
}

function Loading() {
  return <h2>🌀 Yükleniyor...</h2>;
}

Veri yükleyen bileşenler Suspense sınırının doğrudan alt elemanı olmak zorunda değildir. Örneğin, Biography ve Albums’ü yeni bir Details bileşenine taşıyabilirsiniz. Bu davranışı değiştirmez. Biography ve Albums en yakın ebeveyn Suspense sınırını paylaştığı için, açığa çıkışları birlikte koordine edilir.

<Suspense fallback={<Loading />}>
<Details artistId={artist.id} />
</Suspense>

function Details({ artistId }) {
return (
<>
<Biography artistId={artistId} />
<Panel>
<Albums artistId={artistId} />
</Panel>
</>
);
}

İç içe içeriği yüklendikçe açığa çıkarma

Bir bileşen askıya alındığında, en yakın üst Suspense sınırı fallback’i gösterir. Bu, bir yükleme sekansı oluşturmak için birden fazla Suspense sınırını iç içe geçirebilmenizi sağlar. Her Suspense sınırının fallback’i, bir sonraki içerik seviyesi kullanılabilir hale geldikçe doldurulur. Örneğin, albüm listesine kendi fallback’ini verebilirsiniz:

<Suspense fallback={<BigSpinner />}>
<Biography />
<Suspense fallback={<AlbumsGlimmer />}>
<Panel>
<Albums />
</Panel>
</Suspense>
</Suspense>

Bu değişiklikle birlikte, Biography’i göstermek Albums’ün yüklenmesini “beklemek” zorunda değildir.

Sekans şu şekilde olacaktır:

  1. Eğer Biography henüz yüklenmediyse, BigSpinner tüm içerik alanının yerine gösterilir.
  2. Biography yüklenmeyi tamamladığında, BigSpinner içerik ile değiştirilir.
  3. Eğer Albums henüz yüklenmediyse, AlbumsGlimmer Albums ve üst elemanı Panel’in yerine gösterilir.
  4. Son olarak, Albums yüklenmeyi tamamladığında, AlbumsGlimmer’ın yerine geçer.
import { Suspense } from 'react';
import Albums from './Albums.js';
import Biography from './Biography.js';
import Panel from './Panel.js';

export default function ArtistPage({ artist }) {
  return (
    <>
      <h1>{artist.name}</h1>
      <Suspense fallback={<BigSpinner />}>
        <Biography artistId={artist.id} />
        <Suspense fallback={<AlbumsGlimmer />}>
          <Panel>
            <Albums artistId={artist.id} />
          </Panel>
        </Suspense>
      </Suspense>
    </>
  );
}

function BigSpinner() {
  return <h2>🌀 Yükleniyor...</h2>;
}

function AlbumsGlimmer() {
  return (
    <div className="glimmer-panel">
      <div className="glimmer-line" />
      <div className="glimmer-line" />
      <div className="glimmer-line" />
    </div>
  );
}

Suspense sınırları kullanıcı arayüzünüzün hangi parçalarının her zaman birlikte “açığa çıkması” gerektiğini ve hangi parçaların yükleme durumları sekansı içerisinde progresif olarak daha fazla içerik açığa çıkarması gerektiğini koordine etmenizi sağlar. Suspense sınırlarını uygulamanızın geri kalanını etkilemeden ağaç içerisinde herhangi bir yere ekleyebilir, taşıyabilir ya da silebilirsiniz.

Her bileşenin etrafına bir Suspense sınırı koymayın. Suspense sınırları kullanıcıların deneyimlemesini istediğiniz yükleme sekansından daha tanecikli olmamalıdır. Eğer bir tasarımcı ile çalışıyorsanız, yükleme durumlarının nereye konulması gerektiğini sorun—muhtemelen zaten tasarım wireframe’lerine dahil etmişlerdir.


Yeni içerik yüklenirken eski içeriği gösterme

Bu örnekte, SearchResults bileşeni arama sonuçlarını fetch ederken askıya alınır. "a" Yazın, sonuçları bekleyin ve daha sonra yazıyı "ab" olarak düzenleyin. "a" için gelen sonuçlar yükleme fallback’i ile değiştirilecektir.

import { Suspense, useState } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  return (
    <>
      <label>
        Albümleri ara:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Yükleniyor...</h2>}>
        <SearchResults query={query} />
      </Suspense>
    </>
  );
}

Yaygın bir alternatif kullanıcı arayüzü modeli listeyi güncellemeyi ertelemek ve yeni sonuçlar hazır olana kadar önceki sonuçları göstermeye devam etmektir. useDeferredValue Hook’u sorgunun ertelenmiş bir sürümünü aşağıya geçirmenizi sağlar:

export default function App() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
return (
<>
<label>
Albümleri ara:
<input value={query} onChange={e => setQuery(e.target.value)} />
</label>
<Suspense fallback={<h2>Yükleniyor...</h2>}>
<SearchResults query={deferredQuery} />
</Suspense>
</>
);
}

sorgu (query) hemen güncellenecektir, bu yüzden girdi yeni değeri gösterecektir. Ancak, deferredQuery veri yüklenene kadar önceki değerini koruyacaktır, bu yüzden SearchResults bir süreliğine eski sonuçları gösterecektir.

Kullanıcıya daha belli etmek için, eski sonuç listesinin gösterildiği zamanlarda görsel bir gösterge ekleyebilirsiniz:

<div style={{
opacity: query !== deferredQuery ? 0.5 : 1
}}>
<SearchResults query={deferredQuery} />
</div>

Aşağıdaki örneğe "a" yazın, sonuçların yüklenmesini bekleyin, sonrasında girdiyi "ab" olarak değiştirin. Yeni sonuçlar yüklenene kadar Suspense fallback’i yerine soluklaşmış eski sonuç listesini gördüğünüze dikkat edin:

import { Suspense, useState, useDeferredValue } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;
  return (
    <>
      <label>
        Albümleri ara:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Yükleniyor...</h2>}>
        <div style={{ opacity: isStale ? 0.5 : 1 }}>
          <SearchResults query={deferredQuery} />
        </div>
      </Suspense>
    </>
  );
}

Not

Hem geciktirilmiş değerler hem de transition’lar satır içi göstergeler lehine Suspense fallback’inden kaçınmanızı sağlar. Transition’lar tüm güncellemeyi acil olmayan olarak işaretlerler, bu yüzden genellikle framework’ler ve router kütüphaneleri tarafından navigasyon için kullanılırlar. Diğer yandan, geciktirilmiş değerler, genellikle bir kullanıcı arayüzü parçasını acil olmayan olarak işaretlemek ve onu kullanıcı arayüzünün geri kalanından “geride bırakmak” için uygulama kodunda kullanışlıdır.


Zaten açığa çıkmış içeriğin gizlenmesini önleme

Bir bileşen askıya alındığında, en yakın Suspense sınırı fallback’i göstermeye geçer. Bu, zaten bir içerik gösteriliyorsa uyumsuz bir kullanıcı deneyimine yol açabilir. Bu düğmeye basmayı deneyin:

import { Suspense, useState } from 'react';
import IndexPage from './IndexPage.js';
import ArtistPage from './ArtistPage.js';
import Layout from './Layout.js';

export default function App() {
  return (
    <Suspense fallback={<BigSpinner />}>
      <Router />
    </Suspense>
  );
}

function Router() {
  const [page, setPage] = useState('/');

  function navigate(url) {
    setPage(url);
  }

  let content;
  if (page === '/') {
    content = (
      <IndexPage navigate={navigate} />
    );
  } else if (page === '/the-beatles') {
    content = (
      <ArtistPage
        artist={{
          id: 'the-beatles',
          name: 'The Beatles',
        }}
      />
    );
  }
  return (
    <Layout>
      {content}
    </Layout>
  );
}

function BigSpinner() {
  return <h2>🌀 Yükleniyor...</h2>;
}

Butona bastığınızda Router bileşeni ArtistPage sayfası yerine IndexPage sayfasını render etti. ArtistPage içerisindeki bir bileşen askıya alındı, bu yüzden en yakın Suspense sınırı fallback’i göstermeye başladı. En yakın Suspense sınırı köke yakındı, bu yüzden tüm site layout’u BigSpinner ile değiştirildi.

Bunu engellemek için, navigasyon state’ini bir geçiş (transition) olarak startTransition: ile işaretleyebilirsiniz:

function Router() {
const [page, setPage] = useState('/');

function navigate(url) {
startTransition(() => {
setPage(url);
});
}
// ...

Bu, React’e state transition’ının acil olmadığını, ve zaten açığa çıkmış içeriği gizlemek yerine önceki sayfayı göstermeye devam etmenin daha iyi olduğunu söyler. Şimdi butona basmak Biography’nin yüklenmesini “bekler”:

import { Suspense, startTransition, useState } from 'react';
import IndexPage from './IndexPage.js';
import ArtistPage from './ArtistPage.js';
import Layout from './Layout.js';

export default function App() {
  return (
    <Suspense fallback={<BigSpinner />}>
      <Router />
    </Suspense>
  );
}

function Router() {
  const [page, setPage] = useState('/');

  function navigate(url) {
    startTransition(() => {
      setPage(url);
    });
  }

  let content;
  if (page === '/') {
    content = (
      <IndexPage navigate={navigate} />
    );
  } else if (page === '/the-beatles') {
    content = (
      <ArtistPage
        artist={{
          id: 'the-beatles',
          name: 'The Beatles',
        }}
      />
    );
  }
  return (
    <Layout>
      {content}
    </Layout>
  );
}

function BigSpinner() {
  return <h2>🌀 Yükleniyor...</h2>;
}

Bir transition tüm içeriğin yüklenmesini beklemez. Zaten açığa çıkmış içeriği gizlemekten kaçınmak için ne kadar beklemesi gerekiyorsa o kadar bekler. Örneğin, web sitesinin Layout’u zaten açığa çıkmıştı, bu yüzden onu bir yükleniyor çarkının arkasına saklamak kötü olurdu. Bununla birlikte, Albums’ün etrafındaki iç içe geçmiş Suspense sınırı yeni olduğundan, transition onu beklemiyor.

Not

Suspense özellikli router’lar varsayılan olarak navigasyon güncellemelerini transition’lara sararlar.


Transition’ın gerçekleştiğini gösterme

Yukarıdaki örnekte, butona bastığınızda navigasyonun gerçekleştiğini gösteren bir görsel gösterge bulunmamakta. Bir gösterge eklemek için, startTransition’ı useTransition ile değiştirebilirsiniz, bu size bir boolean olan isPending değerini verecektir. Aşağıdaki örnekte, transition’ın gerçekleştiği sırada web sitesi başlığı stilini değiştirmek için useTransition kullanılmıştır:

import { Suspense, useState, useTransition } from 'react';
import IndexPage from './IndexPage.js';
import ArtistPage from './ArtistPage.js';
import Layout from './Layout.js';

export default function App() {
  return (
    <Suspense fallback={<BigSpinner />}>
      <Router />
    </Suspense>
  );
}

function Router() {
  const [page, setPage] = useState('/');
  const [isPending, startTransition] = useTransition();

  function navigate(url) {
    startTransition(() => {
      setPage(url);
    });
  }

  let content;
  if (page === '/') {
    content = (
      <IndexPage navigate={navigate} />
    );
  } else if (page === '/the-beatles') {
    content = (
      <ArtistPage
        artist={{
          id: 'the-beatles',
          name: 'The Beatles',
        }}
      />
    );
  }
  return (
    <Layout isPending={isPending}>
      {content}
    </Layout>
  );
}

function BigSpinner() {
  return <h2>🌀 Yükleniyor...</h2>;
}


Navigasyon sırasında Suspense sınırlarını sıfırlama

Bir transition sırasında, React açığa çıkarılmış içeriği gizlemekten kaçınır. Ancak, farklı parametrelere sahip bir rotaya geçerseniz, React’e bunun farklı içerik olduğunu söylemek isteyebilirsiniz. Bunu bir key ile ifade edebilirsiniz:

<ProfilePage key={queryParams.id} />

Bir kullanıcının profil sayfasına gitmeye çalıştığınızı hayal edin, ve bir şey askıya alınsın. Eğer bu güncelleme bir transition ile sarılırsa, zaten görünen içerik için fallback tetiklenmeyecektir. Bu beklenen davranıştır.

Ancak, şimdi iki farklı kullanıcı profili arasında geçiş yapmaya çalıştığınızı düşünün. Bu durumda, fallback’i göstermek mantıklı olacaktır. Örneğin, bir kullanıcının zaman çizelgesi başka bir kullanıcının zaman çizelgesinden farklı içerik’tir. Bir key belirterek, React’e farklı kullanıcıların profillerini farklı bileşenler olarak ele almasını ve navigasyon sırasında Suspense sınırlarını sıfırlamasını sağlarsınız. Suspense entegreli router’lar bunu otomatik olarak yapmalıdır.


Sunucu hataları ve sadece istemcide olan içerik için bir fallback sağlama

Eğer stream’leyen sunucu render etme API’lerinden birini (ya da onlara bağlı bir framework) kullanıyorsanız, React sunucuda hataları ele almak için <Suspense> sınırlarınızı kullanacaktır. Eğer bir bileşen sunucuda bir hata throw ederse, React sunucu render’ını iptal etmeyecektir. Bunun yerine, onun üzerindeki en yakın <Suspense> bileşenini bulacak ve oluşturulan sunucu HTML’ine bileşenin fallback’ini (örneğin bir yükleniyor çarkı) dahil edecektir. Kullanıcı ilk olarak bir yükleniyor çarkı görecektir.

İstemci tarafında, React aynı bileşeni tekrar render etmeyi deneyecektir. Eğer istemcide de hata verirse, React hatayı throw edip en yakın hata sınırını gösterecektir. Ancak, istemcide hata vermezse, React içeriği nihayetinde başarıyla görüntülediği için hatayı kullanıcıya göstermeyecektir.

Bunu bazı bileşenlerin sunucuda yüklenmemesini sağlamak için kullanabilirsiniz. Bunu yapmak için, sunucu ortamında bir hata throw edin ve ardından HTML’lerini fallback’lerle değiştirmek için <Suspense> sınırı içine alın:

<Suspense fallback={<Loading />}>
<Chat />
</Suspense>

function Chat() {
if (typeof window === 'undefined') {
throw Error('Chat bileşeni sadece istemcide render edilmelidir.');
}
// ...
}

Sunucu HTML’i yükleniyor çarkını içerecektir. İstemci tarafında yükleniyor çarkı Chat bileşeni ile değiştirilecektir.


Hata ayıklama

Kullanıcı arayüzünün bir güncelleme sırasında bir fallback ile değiştirilmesini nasıl engellerim?

Görünür bir kullanıcı arayüzünü bir fallback ile değiştirmek, uyumsuz bir kullanıcı deneyimine sebep olur. Bu, bir güncelleme bir bileşenin askıya alınmasına sebep olduğunda ve en yakın Suspense sınırı zaten kullanıcıya içerik gösteriyorsa olabilir.

Bunun olmasını engellemek için, güncellemeyi startTransition ile acil olmayan olarak işaretleyin. Bir transition sırasında, React istenmeyen bir fallback’in görünmesini engellemek için yeterli veri yüklenene kadar bekleyecektir:

function handleNextPageClick() {
// Eğer bu güncelleme askıya alınırsa, zaten görünen içeriği gizleme
startTransition(() => {
setCurrentPage(currentPage + 1);
});
}

Bu, varolan içeriği gizlemeyi önleyecektir. Ancak, yeni render edilen Suspense sınırları hala kullanıcı arayüzünü bloke etmemek ve kullanıcının içeriği hazır hale geldikçe görmesini sağlamak için hemen fallback gösterecektir.

React sadece istenmeyen fallback’leri acil olmayan güncellemeler sırasında engeller. Eğer acil bir güncelleme sonucunda gerçekleşiyorsa, bir render’ı geciktirmeyecektir. startTransition veya useDeferredValue gibi bir API tercih etmeniz gerekecektir.

Eğer router’ınız Suspense ile entegre ise, güncellemelerini startTransition’ın içerisine otomatik olarak sarması gerekmektedir.