createResource membungkus fungsi async yang mengambil data (disebut fetcher) dan memberimu dua hal: pembaca datanya, dan status pengambilannya, yaitu sedang memuat, gagal, atau sudah siap. Pasangannya <Suspense>, yang menampilkan tulisan pengganti selama datanya belum datang. Bayangin Kak Nadia memesan katalog dari pemasok lewat telepon. Selama menunggu, di rak dipasang papan Memuat katalog.... Slip pesanannya mencatat statusnya: masih menunggu, gagal, atau sudah sampai. Kalau pembeli minta judul lain, Kak Nadia menelepon lagi dengan kata yang baru.
🧪 Tebakan cepat: katalog Toko Buku Kak Nadia dimuat dengan
createResource(cari, ambilKatalog)di dalam<Suspense fallback={<p>Memuat katalog...</p>}>, dan daftarnya membacabuku(). Katalog sudah tampil, lalu kamu mengetikbumidi kotak cari. Selama data barunya dimuat, daftar lama tetap tampil, atau papanMemuat katalog...muncul lagi? Jawabannya ada di bagian "Saat data dimuat ulang".
createResource dan Suspense
import { createResource, Suspense, Show } from "solid-js"
const fetchData = async () => {
return { name: "Data Berhasil" }
}
function App() {
const [data] = createResource(fetchData)
return (
<Suspense fallback={<p>Loading...</p>}>
<Show when={data()}>
<p>{data().name}</p>
</Show>
</Suspense>
)
}
| Saat | data() |
Halaman |
|---|---|---|
| Komponen jalan, fetcher dipanggil | undefined |
Loading... |
| Promise selesai | { name: "Data Berhasil" } |
Data Berhasil |
createResource(fetchData)memanggilfetchDatasekali dan mengembalikan[data, { refetch, mutate }].datadibaca seperti signal:data().- Sebelum promise-nya selesai,
data()bernilaiundefined.<Show when={data()}>menjaga supayadata().namegak dibaca terlalu cepat. <Suspense>menampilkanfallbackselama resource yang dibaca di dalamnya belum siap, lalu menggantinya sendiri dengan isinya. Ini latihan pertama: yang terbaca di akhirData Berhasil, bukanLoading....
Status sebuah resource
Selain data(), resource punya properti status. Ini properti biasa, dibaca tanpa kurung:
| Properti | Isinya |
|---|---|
data.loading |
true selama fetcher belum selesai |
data.error |
objek error kalau fetcher gagal |
data.state |
"pending" (pertama kali), "ready", "refreshing" (dimuat ulang), "errored" |
data.latest |
data terakhir yang berhasil, tetap ada selama dimuat ulang |
Latihan kedua menampilkan tiga keadaan itu dengan <Show>, tanpa <Suspense>:
import { createResource, Show } from "solid-js"
const fetchUser = async () => ({ name: "Andi", email: "[email protected]" })
function App() {
const [user] = createResource(fetchUser)
return (
<div>
<Show when={user.loading}><p>Loading...</p></Show>
<Show when={user.error}><p>Error!</p></Show>
<Show when={!user.error && user()}>
<p>{user().name} - {user().email}</p>
</Show>
</div>
)
}
Hasilnya Andi - [email protected]. Kenapa !user.error && di Show ketiga? Resource yang gagal melempar error-nya saat dibaca dengan user(). Diuji di Solid 1.9: tanpa penjaga itu, fetcher yang gagal menghentikan halaman dengan Error: Server sibuk, padahal Error! sudah siap ditampilkan.
Sumber: ambil ulang saat signal berubah
Katalog Toko Buku Kak Nadia yang mengikuti kotak cari. Sandbox gak punya jaringan, jadi "server"-nya Promise yang selesai 800 ms kemudian. Di aplikasi asli, isinya fetch("/api/buku?cari=" + cari).then(r => r.json()).
import { createSignal, createResource, For, Suspense } from "solid-js"
const katalog = [
{ judul: "Laskar Pelangi", harga: 89000 },
{ judul: "Bumi Manusia", harga: 120000 },
{ judul: "Cantik Itu Luka", harga: 95000 },
]
function ambilKatalog(cari) {
console.log("fetcher jalan, cari:", JSON.stringify(cari))
return new Promise(resolve => {
setTimeout(() => resolve(katalog.filter(b => b.judul.toLowerCase().includes(cari.toLowerCase()))), 800)
})
}
function App() {
const [cari, setCari] = createSignal("")
const [buku, { refetch }] = createResource(cari, ambilKatalog)
return (
<div>
<input placeholder="Cari judul" onInput={e => setCari(e.target.value)} />
<button onClick={refetch}>Muat ulang</button>
<Suspense fallback={<p>Memuat katalog...</p>}>
<ul>
<For each={buku()}>{(b) => <li>{b.judul} · Rp{b.harga}</li>}</For>
</ul>
</Suspense>
</div>
)
}
createResource(cari, ambilKatalog): argumen pertama sumbernya, sebuah signal. Fetcher menerima nilainya,ambilKatalog(""), lalu dipanggil lagi tiap kali sumbernya berubah: ketikbumi, console mencetakfetcher jalan, cari: "bumi".- Kalau sumbernya
false,null, atauundefined, fetcher gak dipanggil. String kosong tetap dipanggil. refetch()memanggil fetcher lagi dengan sumber yang sama, misalnya untuk tombolMuat ulang.
Saat data dimuat ulang
Dimuat ulang (ketik di kotak cari atau klik Muat ulang), diuji di Solid 1.9:
Isi <Suspense> |
Selama memuat ulang | Sesudahnya |
|---|---|---|
<For each={buku()}> |
daftar hilang, muncul Memuat katalog... |
Bumi Manusia · Rp120000 |
<Show when={buku.loading}><p>Memperbarui...</p></Show> + <For each={buku.latest}> |
daftar lama tetap, plus Memperbarui... |
Bumi Manusia · Rp120000 |
Dengan buku(), papan Memuat katalog... muncul lagi tiap dimuat ulang. Itu jawaban tebakannya. buku() yang dibaca selama status "refreshing" membuat <Suspense> menunggu lagi. buku.latest mengembalikan data terakhir tanpa menunggu, jadi daftar lama tetap ada. Fallback cuma tampil saat pemuatan pertama.
Kalau fetcher gagal
Kalau promise-nya ditolak, buku.state jadi "errored", buku.error berisi error-nya, dan membaca buku() atau buku.latest melempar error itu ke <ErrorBoundary> terdekat:
<ErrorBoundary fallback={(err, reset) => (
<p>Gagal: {err.message} <button onClick={() => { refetch(); reset() }}>Coba lagi</button></p>
)}>
<Suspense fallback={<p>Memuat katalog...</p>}>...</Suspense>
</ErrorBoundary>
reset() cuma menggambar ulang isinya, jadi panggil refetch() juga, kalau gak, error yang sama langsung muncul lagi. ErrorBoundary dibahas lengkap di pelajaran Error Boundaries.
⚠️ Jebakan yang sering ketemu
buku.loading()dengan kurung.loading,error,state, danlatestitu properti, bukan fungsi.user.loading()berhenti denganTypeError: user.loading is not a function. Yang dipanggil cuma resource-nya:user().- Membaca isinya sebelum siap.
<p>{data().name}</p>tanpaShowberhenti denganTypeError: Cannot read properties of undefined (reading 'name'). - Signal dibaca di dalam fetcher.
createResource(() => ambilKatalog(cari()))cuma mengambil sekali: bacaan di dalam fetcher gak dilacak. Jadikan signal-nya sumber:createResource(cari, ambilKatalog). - Mengharap daftar lama tetap tampil. Dengan
buku()di dalam<Suspense>, tiap muat ulang menampilkan fallback. Pakaibuku.latestkalau daftar lamanya harus tetap ada. - Menilai tulisan penunggunya. Latihan pertama mencari
Data Berhasildi<p>, latihan keduaAndidi halaman.Loading...saja gak lolos.
TL;DR: const [data] = createResource(fetchData) memanggil fetcher async dan memberi data() (undefined sampai siap) plus properti data.loading, data.error, data.state, data.latest, dibaca tanpa kurung. <Suspense fallback={...}> menampilkan fallback selama resource yang dibaca di dalamnya belum siap; <Show when={data()}> menjaga bacaan isinya. createResource(cari, ambilKatalog) memanggil fetcher lagi tiap sumbernya berubah, refetch() memanggilnya lagi dengan sumber yang sama. Saat dimuat ulang, buku() memunculkan fallback lagi, buku.latest mempertahankan data lama. Resource yang gagal melempar error-nya saat dibaca: tangkap dengan ErrorBoundary, atau jaga dengan !user.error &&.