Di SolidStart, data toko disimpan di server, dan halaman memintanya lewat tiga alat dari @solidjs/router: query untuk fungsi pengambil data, createAsync untuk membacanya di komponen, dan action untuk mengubahnya. Fungsi yang ditandai "use server" cuma jalan di server. Bayangin kasir Toko Buku Kak Nadia dan gudangnya. Kasir (halaman) gak boleh membuka lemari gudang (database) sendiri, jadi dia menelepon gudang. Jawaban gudang dicatat di kertas kecil di meja kasir, supaya dua pembeli yang menanyakan hal yang sama gak bikin dua telepon. Waktu ada pesanan, kasir mengirim slip ke gudang, lalu menelepon lagi untuk angka stok terbaru.
🧪 Tebakan cepat: daftar buku dimuat dengan
createAsync(() => getKatalog()). Kamu menekan Tambah di Laskar Pelangi (stok 4), danaction-nya mengurangi stok di server. Kamu gak menulis kode apa pun untuk memuat ulang daftarnya. Sesudah action selesai, daftarnya menampilkan stok 3, atau tetap 4? Jawabannya ada di bagian "Sesudah action: revalidasi".
"use server": kode yang cuma jalan di server
// src/lib/toko.ts
import { query } from "@solidjs/router"
import { db } from "./db"
export const getKatalog = query(async () => {
"use server"
return db.semuaBuku()
}, "katalog")
"use server"ditulis sebagai baris pertama badan fungsi. SolidStart memisahkan fungsi itu: badannya cuma ada di server, dan browser mendapat penggantinya yang mengirim request ke server lalu menerima hasilnya.- Jadi kode dan data rahasia di dalamnya, koneksi database atau kunci API, gak pernah ikut terkirim ke browser. Ini jawaban kuis: kode dengan
"use server"hanya berjalan di server. - Fungsinya harus
async. Argumen dan hasilnya dikirim lewat jaringan, jadi isinya data biasa: angka, teks, objek, array.
query dan createAsync: membaca data di halaman
// src/routes/index.tsx
import { createAsync } from "@solidjs/router"
import { For, Suspense } from "solid-js"
import { getKatalog } from "~/lib/toko"
export default function Beranda() {
const buku = createAsync(() => getKatalog())
return (
<Suspense fallback={<p>Memuat katalog...</p>}>
<ul>
<For each={buku()}>{(b) => <li>{b.judul} · stok {b.stok}</li>}</For>
</ul>
</Suspense>
)
}
Diuji dengan @solidjs/router 1.0 dan server palsu yang menjawab 800 ms kemudian:
| Waktu | buku() |
Halaman |
|---|---|---|
| 0 ms | undefined, getKatalog dipanggil |
Memuat katalog... |
| 800 ms | array 3 buku | Laskar Pelangi · stok 4, Bumi Manusia · stok 2, Cantik Itu Luka · stok 0 |
query(fungsi, "katalog")membungkus fungsi pengambil data dan menyimpan hasilnya dengan nama"katalog". Diuji: dua komponen yang sama-sama memanggilgetKatalog()di satu halaman cuma memicu satu request. Di versi lama namanyacache.createAsync(() => getKatalog())mengembalikan pembaca, seperti signal:buku(). Isinyaundefinedsampai datanya datang. Dibaca di dalam<Suspense>, fallback tampil selama itu.- Inilah jawaban kuis tentang pemuatan data:
createAsyncdenganquery. Di SolidStart,createAsyncmenggantikancreateResourcekarena dia bekerja sama dengan router.
action: mengubah data lewat form
import { action, useSubmission } from "@solidjs/router"
export const tambahKeKeranjang = action(async (formData) => {
"use server"
const id = Number(formData.get("id"))
const buku = await db.cariBuku(id)
if (buku.stok === 0) throw new Error("Stok habis")
await db.tambahKeKeranjang(id)
}, "tambahKeKeranjang")
Di halaman, tiap buku punya form kecil:
const kirim = useSubmission(tambahKeKeranjang)
<Show when={kirim.pending}><p>Menambahkan...</p></Show>
<Show when={kirim.error}><p>Gagal: {kirim.error.message}</p></Show>
<form action={tambahKeKeranjang} method="post">
<input type="hidden" name="id" value={b.id} />
<button type="submit">Tambah</button>
</form>
action(fungsi, "nama")membuat aksi.<form action={tambahKeKeranjang} method="post">mengirim isi form-nya ke fungsi itu sebagaiFormData, danformData.get("id")membaca kotak bernamaid.useSubmission(tambahKeKeranjang)melaporkan kiriman terakhir:kirim.pendingbernilaitrueselama action jalan,kirim.errorberisi error yang dilemparnya.- Server tetap memeriksa stok. Tombol di Cantik Itu Luka (stok 0) menghasilkan
Gagal: Stok habis.
Sesudah action: revalidasi
Kamu menekan Tambah di Laskar Pelangi. Action-nya 300 ms, getKatalog 800 ms, getKeranjang 200 ms. Diuji dengan @solidjs/router 1.0:
| Sejak klik | Yang terjadi | Halaman |
|---|---|---|
| 0 ms | form dikirim, action jalan di server | Menambahkan..., daftar lama tetap |
| 300 ms | action selesai, router memanggil lagi getKatalog dan getKeranjang |
masih daftar lama |
| 1.100 ms | data baru datang | Laskar Pelangi · stok 3, Keranjang: 1 buku, Menambahkan... hilang |
Stoknya turun jadi 3 sendiri. Itu jawaban tebakannya. Sesudah action berhasil, router merevalidasi (memuat ulang) semua query di halaman itu, jadi daftar dan keranjang cocok lagi dengan server. Selama itu daftar lama tetap tampil, fallback gak muncul lagi.
- Action yang melempar error gak memicu revalidasi. Datanya tetap, dan pesan error-nya ada di
kirim.error. - Mau memuat ulang sebagian saja? Kembalikan
json(hasil, { revalidate: getKeranjang.key }). Diuji: cuma keranjang yang dimuat ulang, dan stok di daftar tetap 4 walau di server sudah 3.
Latihan: query dan createAsync di sandbox
Sandbox latihan gak punya server SolidStart, tapi query dan createAsync di sana bekerja seperti di @solidjs/router: pemanggilan dengan argumen yang sama berbagi satu request, dan createAsync menahan <Suspense> sampai datanya datang. Fungsi "use server" jalan di pratinjau.
const getItems = query(async () => {
"use server"
setPermintaan((n) => n + 1)
await new Promise((r) => setTimeout(r, 200))
return [
{ id: 1, name: "Item 1" },
{ id: 2, name: "Item 2" },
]
}, "items")
function Daftar() {
const items = createAsync(() => getItems())
return (
<ul>
<For each={items()}>{(item) => <li>{item.name}</li>}</For>
</ul>
)
}
Komponen Jumlah memanggil createAsync(() => getItems()) juga, tapi penghitungnya tetap Permintaan ke server: 1: query membagi jawabannya ke dua komponen. Tanpa query, fungsi yang sama ditanya dua kali.
⚠️ Jebakan yang sering ketemu
"use server"bukan di baris pertama. Ditulis sesudah baris lain, dia cuma string biasa, dan fungsinya ikut jalan di browser, tanpa akses ke database.- Membaca isinya sebelum datang.
buku().lengthsebelum datanya siap berhenti denganTypeError: Cannot read properties of undefined (reading 'length').<For each={buku()}>aman, dan<Show when={buku()}>menjaga bacaan lain. - Mengira action yang gagal tetap memuat ulang data. Gak ada revalidasi. Tampilkan
kirim.error. createAsynctanpaquery. Datanya tetap tampil, tapi tiap komponen meminta sendiri ke server, jadi#permintaanberbunyiPermintaan ke server: 2.
TL;DR: Fungsi dengan "use server" di baris pertamanya cuma jalan di server; browser memanggilnya lewat request. query(fungsi, "katalog") membungkus pengambil data dan menyimpan hasilnya per nama, dan createAsync(() => getKatalog()) membacanya di halaman: undefined dulu, dibaca di dalam <Suspense>. action(fungsi, "nama") menerima FormData dari <form action={...} method="post">, useSubmission memberi pending dan error, dan sesudah action berhasil router merevalidasi semua query di halaman, jadi stok dan keranjang ikut berubah sendiri. Di sandbox latihan, query dan createAsync bekerja sama, dan fungsi "use server" jalan di pratinjau.