<Suspense> memungkinkan Anda menampilkan fallback sampai komponen anak-anaknya selesai dimuat.

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

Referensi

<Suspense>

Props

  • children: UI sebenarnya yang ingin Anda render. Jika children ditangguhkan saat di-render, maka Suspense akan beralih me-render fallback.
  • fallback: UI alternatif untuk di-render menggantikan UI yang sebenarnya jika belum selesai dimuat. Simpul React apapun yang valid akan diterima, meskipun dalam praktiknya, fallback adalah tampilan pengganti yang ringan, Suspense akan secara otomatis beralih ke fallback ketika children ditangguhkan, dan kembali ke children ketika datanya sudah siap. Jika fallback ditangguhkan sewaktu melakukan rendering, induk terdekat dari batasan Suspense akan diaktifkan.

Catatan Penting

  • React tidak menyimpan state apa pun untuk render-an yang ditangguhkan sebelum dapat dimuat untuk pertama kalinya. Ketika komponen sudah dimuat, React akan mencoba me-render ulang komponen yang ditangguhkan dari awal.
  • Jika Suspense menampilkan konten untuk komponen, namun kemudian ditangguhkan lagi, fallback akan ditampilkan kembali kecuali jika pembaruan yang menyebabkannya, disebabkan oleh startTransition atau useDeferredValue.
  • Jika React perlu menyembunyikan konten yang sudah terlihat karena ditangguhkan kembali, React akan membersihkan layout Effects yang ada di dalam konten komponen. Ketika konten siap untuk ditampilkan lagi, React akan menjalankan layout Effects lagi. Hal ini memastikan bahwa Efek yang mengukur tata letak DOM tidak mencoba melakukan hal ini saat konten disembunyikan.
  • React menyertakan pengoptimalan di balik layar seperti Streaming Server Rendering dan Selective Hydration yang terintegrasi dengan Suspense. Baca tinjauan arsitektur dan tonton diskusi teknis untuk mempelajari lebih lanjut.

Pengunaan

Menampilkan fallback saat konten sedang dimuat

Anda dapat membungkus bagian mana pun dari aplikasi Anda dengan batasan Suspense:

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

React akan menampilkan fallback pemuatan sampai semua kode dan data yang dibutuhkan oleh anak-anaknya telah selesai dimuat.

Pada contoh di bawah ini, Komponen Albums ditangguhkan saat mengambil daftar album. Hingga siap untuk di-render, React mengganti batasan Suspense terdekat di atas untuk menunjukkan fallback, komponen Loading Anda. Kemudian, saat data termuat, React menyembunyikan fallback Loading dan me-render komponen Albums dengan data tersebut.

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>🌀 Memuat...</h2>;
}

Note

Hanya sumber data yang mendukung Suspense yang akan mengaktifkan komponen Suspense. Sumber data tersebut antara lain:

Suspense tidak mendeteksi ketika data diambil di dalam Effect atau event handler.

Cara yang tepat untuk memuat data dalam komponen Albums di atas tergantung pada framework Anda. Jika Anda menggunakan framework yang mendukung Suspense, Anda akan menemukan detailnya dalam dokumentasi pengambilan data.

Pengambilan data yang mendukung Suspense tanpa menggunakan framework yang mendukung belum didukung. Persyaratan untuk mengimplementasikan sumber data yang mendukung Suspense masih belum stabil dan belum terdokumentasi. API resmi untuk mengintegrasikan sumber data dengan Suspense akan dirilis pada versi React yang akan datang.


Menampilkan konten secara bersamaan sekaligus

Secara default, seluruh pohon di dalam Suspense diperlakukan sebagai satu kesatuan. Sebagai contoh, meskipun hanya satu dari komponen-komponen ini yang tertahan menunggu beberapa data, semua komponen tersebut akan digantikan oleh indikator pemuatan:

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

Kemudian, setelah semuanya siap untuk ditampilkan, semuanya akan muncul sekaligus.

Pada contoh di bawah ini, baik Biography dan Album mengambil beberapa data. Namun, karena mereka dikelompokkan di bawah satu batasan Suspense, komponen-komponen ini akan selalu “muncul” bersamaan.

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>🌀 Memuat...</h2>;
}

Komponen yang memuat data tidak harus menjadi anak langsung dari batasan Suspense. Sebagai contoh, Anda dapat memindahkan Biografi dan Album ke dalam komponen Rincian yang baru. Hal ini tidak akan mengubah perilakunya. Biografi dan Albums memiliki batasan Suspense induk terdekat yang sama, sehingga pemunculannya dikoordinasikan bersama-sama.

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

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

Mengungkap konten yang tersusun saat dimuat

Ketika sebuah komponen ditangguhkan, komponen Suspense induk terdekat akan menampilkan fallback. Hal ini memungkinkan Anda menyatukan beberapa komponen Suspense untuk membuat urutan pemuatan. Fallback setiap batas Suspense akan terisi saat tingkat konten berikutnya tersedia. Sebagai contoh, Anda dapat memberikan daftar album dengan fallback tersendiri:

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

Dengan perubahan ini, menampilkan Biography tidak perlu “menunggu” sampai Album dimuat.

Urutan nya adalah sebagai berikut:

  1. Jika Biography belum dimuat, BigSpinner ditampilkan sebagai pengganti seluruh area konten.
  2. Setelah Biography selesai dimuat, BigSpinner digantikan oleh konten.
  3. Jika Albums belum dimuat, AlbumsGlimmer ditampilkan sebagai pengganti Albums dan induknya Panel.
  4. Akhirnya, setelah Albums selesai dimuat, dia akan menggantikan AlbumsGlimmer.
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>🌀 Memuat...</h2>;
}

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

Batas suspense memungkinkan Anda mengoordinasikan bagian mana dari UI Anda yang harus selalu “muncul” bersamaan, dan bagian mana yang harus menampilkan lebih banyak konten secara bertahap dalam urutan status pemuatan. Anda dapat menambah, memindahkan, atau menghapus batas-batas Suspense di mana saja di dalam pohon tanpa memengaruhi perilaku aplikasi Anda yang lain.

Jangan memberikan batas Suspense pada setiap komponen. Batas suspense tidak boleh lebih terperinci daripada urutan pemuatan yang Anda inginkan untuk dialami pengguna. Jika Anda bekerja dengan desainer, tanyakan kepada mereka di mana status pemuatan harus ditempatkan - kemungkinan mereka sudah memasukkannya dalam wireframe desain mereka.


Menampilkan konten yang sudah basi saat konten baru sedang dimuat

Dalam contoh ini, komponen SearchResults ditangguhkan saat mengambil hasil pencarian. Ketik "a", tunggu untuk hasil, dan kemudian edit menjadi "ab". Hasil untuk "a" akan tergantikan oleh loading fallback.

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

export default function App() {
  const [query, setQuery] = useState('');
  return (
    <>
      <label>
        Search albums:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Loading...</h2>}>
        <SearchResults query={query} />
      </Suspense>
    </>
  );
}

Pola UI alternatif yang umum adalah untuk menunda memperbarui daftar dan terus menampilkan hasil sebelumnya hingga hasil yang baru siap. The useDeferredValue Hook memungkinkan Anda meneruskan versi kueri yang ditangguhkan:

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

query akan segera diperbarui, sehingga input akan menampilkan nilai baru. Namun, deferredQuery akan menyimpan nilai sebelumnya sampai data dimuat, jadi SearchResults akan menunjukkan hasil yang sebelumnya untuk sementara waktu.

Untuk membuatnya lebih jelas bagi pengguna, Anda bisa menambahkan indikasi visual apabila daftar hasil basi ditampilkan:

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

Masukkan "a" didalam contoh berikut ini, tunggu hingga hasilnya dimuat, lalu edit input ke "ab". Perhatikan, bahwa alih-alih fallback Suspense, Anda sekarang melihat daftar hasil sebelumnya yang diredupkan sampai hasil yang baru dimuat:

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>
        Search albums:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Loading...</h2>}>
        <div style={{ opacity: isStale ? 0.5 : 1 }}>
          <SearchResults query={deferredQuery} />
        </div>
      </Suspense>
    </>
  );
}

Note

Baik nilai yang ditangguhkan maupun transitions memungkinkan Anda menghindari menampilkan Suspense fallback demi indikator sebaris. Transisi menandai seluruh pembaruan sebagai tidak mendesak sehingga biasanya digunakan oleh framework dan pustaka router untuk navigasi. Nilai yang ditangguhkan, di sisi lain, sebagian besar berguna dalam kode aplikasi di mana Anda ingin menandai bagian dari UI sebagai tidak mendesak dan membiarkannya “tertinggal” dari UI lainnya.


Mencegah konten yang sudah terungkap agar tidak disembunyikan

Ketika sebuah komponen ditangguhkan, batas Suspense induk terdekat akan beralih untuk menampilkan fallback. Hal ini dapat menyebabkan pengalaman pengguna yang mengejutkan jika komponen tersebut sudah menampilkan beberapa konten. Coba tekan tombol ini:

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>🌀 Memuat...</h2>;
}

Saat Anda menekan tombol, Router komponen me-render ArtistPage sebagai gantinya IndexPage.Komponen di dalam ArtistPage tertangguhkan, sehingga batas Suspense terdekat mulai menunjukkan fallback. Batas Suspense terdekat berada di dekat root, sehingga seluruh tata letak situs diganti dengan BigSpinner.

Untuk mencegah hal ini, Anda dapat menandai pembaruan status navigasi sebagai transition dengan startTransition:

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

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

Hal ini memberi tahu React bahwa transisi state tidak mendesak, dan lebih baik tetap menampilkan halaman sebelumnya daripada menyembunyikan konten yang sudah ditampilkan. Sekarang klik tombol “menunggu” sampai Biography dimuat:

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>🌀 Memuat...</h2>;
}

Transisi tidak menunggu semua konten dimuat. Ini hanya menunggu cukup lama untuk menghindari menyembunyikan konten yang sudah terungkap. Misalnya, situs web Layout sudah terungkap, jadi tidak baik menyembunyikannya di balik loading spinner. Namun, batas Suspense yang bersusun di sekitar Albums adalah hal yang baru, jadi transisinya tidak perlu ditunggu.

Note

Router yang mendukung suspense diharapkan untuk membungkus pembaruan navigasi ke dalam transisi secara bawaan.


Mengindikasikan bahwa transisi sedang terjadi

Pada contoh di atas, setelah Anda mengeklik tombol, tidak ada indikasi visual bahwa navigasi sedang berlangsung. Untuk menambahkan indikator, Anda dapat mengganti startTransition dengan useTransition yang akan memberimu boolean dengan nilai isPending. Pada contoh di bawah ini, ini digunakan untuk mengubah gaya tajuk situs web saat transisi terjadi:

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>🌀 Memuat...</h2>;
}


Menyetel ulang batas Suspense pada navigasi

Selama transisi, React akan menghindari menyembunyikan konten yang sudah ditampilkan. Namun, jika Anda menavigasi ke rute dengan parameter yang berbeda, Anda mungkin ingin memberi tahu React bahwa itu adalah konten yang berbeda. Anda dapat mengekspresikan ini dengan sebuah key:

<ProfilePage key={queryParams.id} />

Bayangkan Anda sedang menavigasi dalam halaman profil pengguna, dan ada sesuatu yang ditangguhkan. Jika pembaruan itu dibungkus dengan transisi, pembaruan itu tidak akan memicu kemunduran untuk konten yang sudah terlihat. Itulah perilaku yang diharapkan.

Namun, sekarang bayangkan Anda menavigasi di antara dua profil pengguna yang berbeda. Dalam hal ini, masuk akal untuk menampilkan fallback. Sebagai contoh, timeline salah satu pengguna adalah konten yang berbeda dengan timeline pengguna lain. Dengan menentukan sebuah kunci, Anda memastikan bahwa React memperlakukan profil pengguna yang berbeda sebagai komponen yang berbeda, dan menyetel ulang batas-batas Suspense selama navigasi. Router yang terintegrasi dengan Suspense seharusnya melakukan ini secara otomatis.


Menyediakan fallback untuk kesalahan server dan konten khusus server

Jika Anda menggunakan salah satu dari API perenderan server streaming (atau framework yang bergantung pada mereka), React juga akan menggunakan <Suspense> untuk menangani kesalahan pada server. Jika sebuah komponen menimbulkan kesalahan pada server, React tidak akan membatalkan render server. Sebagai gantinya, React akan menemukan komponen <Suspense> terdekat di atasnya dan menyertakan fallback-nya (seperti spiner) ke dalam HTML server yang dihasilkan. Pengguna akan melihat pemintal pada awalnya.

Pada klien, React akan mencoba me-render komponen yang sama lagi. Jika terjadi kesalahan pada klien juga, React akan melemparkan kesalahan dan menampilkan [ErrorBoundary terdekat.] (/reference/react/Component/Component#static-getderivedstatefromerror) Namun, jika tidak terjadi kesalahan pada klien, React tidak akan menampilkan kesalahan pada pengguna karena konten pada akhirnya berhasil ditampilkan.

Anda dapat menggunakan ini untuk mengecualikan beberapa komponen dari perenderan di server. Untuk melakukan hal ini, lemparkan kesalahan pada lingkungan server dan kemudian bungkus dengan batas <Suspense> untuk mengganti HTML-nya dengan fallback:

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

function Chat() {
if (typeof window === 'undefined') {
throw Error('Chat should only render on the client.');
}
// ...
}

HTML server akan menyertakan indikator pemuatan. Indikator ini akan digantikan oleh komponen Chat pada klien.


Pemecahan Masalah

Bagaimana cara mencegah agar UI tidak diganti dengan fallback selama pembaruan?

Mengganti UI yang terlihat dengan fallback menciptakan pengalaman pengguna yang mengejutkan. Hal ini dapat terjadi ketika pembaruan menyebabkan sebuah komponen ditangguhkan, dan batas Suspense terdekat sudah menampilkan konten kepada pengguna.

Untuk mencegah hal ini terjadi, tandai pembaruan sebagai tidak mendesak menggunakan startTransition. Selama transisi, React akan menunggu hingga cukup banyak data yang dimuat untuk mencegah terjadinya fallback yang tidak diinginkan:

function handleNextPageClick() {
// If this update suspends, don't hide the already displayed content
startTransition(() => {
setCurrentPage(currentPage + 1);
});
}

Ini akan menghindari menyembunyikan konten yang ada. Namun, setiap batas Suspense yang baru di-render masih akan segera menampilkan fallback untuk menghindari pemblokiran UI dan membiarkan pengguna melihat konten saat tersedia.

React hanya akan mencegah fallback yang tidak diinginkan selama pembaruan yang tidak mendesak. Ini tidak akan menunda render jika itu adalah hasil dari pembaruan yang mendesak. Anda harus ikut serta dengan API seperti startTransition atau useDeferredValue.

Jika router Anda terintegrasi dengan Suspense, router akan membungkus pembaruannya menjadi startTransition secara otomatis.