Context adalah cara sebuah komponen menitipkan nilai untuk semua keturunannya: anak, cucu, dan seterusnya, tanpa mengopernya lewat props di tiap tingkat. Induk memanggil setContext(kunci, nilai), dan keturunan mana pun mengambilnya dengan getContext(kunci). Bayangin papan pengumuman di dapur Warung Bu Sri. Pagi-pagi Bu Sri menulis "harga ditulis Rp 15.000, bukan 15000" dan menempelnya sekali. Semua orang di dapur itu tinggal membaca papannya, tanpa pesan berantai dari orang ke orang. Dapur cabang lain punya papannya sendiri. Tapi papan itu kertas: kalau siang hari Bu Sri berubah pikiran dan gak menulis ulang papannya, yang membaca tetap melihat tulisan pagi.
🧪 Tebakan cepat:
App.sveltepunyalet tema = "terang"dan menitipkannya dengansetContext("tema", tema).MenuItem.sveltemembacanya dengangetContext("tema")dan menampilkannya. Tombol Ganti tema di App mengubahtemajadi"gelap". MenuItem ikut jadi gelap, atau tetap terang? Jawabannya ada di bagian "Context gak reaktif: titipkan store".
Masalahnya: prop drilling
Warung Bu Sri punya tiga tingkat komponen: App.svelte memasang DaftarMenu.svelte, dan DaftarMenu memasang satu MenuItem.svelte per menu. MenuItem butuh function rupiah untuk menulis harga, padahal function itu ada di App. Dengan props, DaftarMenu harus menerima rupiah lalu mengopernya lagi ke tiap MenuItem, walaupun DaftarMenu sendiri gak pernah memakainya. Mengoper props lewat komponen yang cuma jadi perantara seperti ini disebut prop drilling. Makin dalam pohon komponennya, makin banyak perantara yang harus ikut diubah.
setContext dan getContext
<!-- App.svelte -->
<script>
import { setContext } from "svelte"
import DaftarMenu from "./DaftarMenu.svelte"
function rupiah(angka) {
return "Rp " + angka.toLocaleString("id-ID")
}
setContext("rupiah", rupiah)
</script>
<h1>Warung Bu Sri</h1>
<DaftarMenu />
<!-- MenuItem.svelte -->
<script>
import { getContext } from "svelte"
export let nama
export let harga
const rupiah = getContext("rupiah")
</script>
<li>{nama}: {rupiah(harga)}</li>
DaftarMenu cuma menulis <MenuItem nama={m.nama} harga={m.harga} /> di dalam {#each}, tanpa menyebut rupiah sama sekali. Hasilnya Nasi Goreng: Rp 15.000, Mie Ayam: Rp 12.000, Es Teh: Rp 5.000.
- Dua-duanya diimpor dari
"svelte". setContext(kunci, nilai): kuncinya biasanya string, nilainya boleh apa saja: teks, objek, function, atau store.getContext(kunci)mencari ke atas: komponen itu sendiri, induknya, induk dari induknya, sampai ketemu. Kalau dua komponen menitipkan kunci yang sama, yang paling dekat yang menang.- Arahnya cuma ke bawah. App yang membaca kunci yang cuma dititipkan anaknya mendapat
undefined. - Kuncinya harus sama persis, termasuk huruf besar-kecil.
setContext("Theme", "dark")lalugetContext("theme")menghasilkanTema: undefined.
Cuma saat komponen dibuat
setContext dan getContext harus dipanggil langsung di <script>, saat komponen sedang dibuat. Di dalam handler klik, setTimeout, atau setelah await, Svelte 4 menolak dengan error:
Function called outside component initialization
Jadi baca context-nya sekali di awal ke sebuah const, lalu pakai const itu di handler: const rupiah = getContext("rupiah"), kemudian on:click={() => alert(rupiah(harga))}.
Context gak reaktif: titipkan store
setContext("tema", tema) menitipkan nilai tema pada saat itu, yaitu string "terang". Assignment tema = "gelap" di App sesudahnya cuma mengubah variabel App. Yang dititipkan tetap "terang", jadi MenuItem tetap terang. Itu jawaban tebakannya.
Supaya keturunannya ikut berubah, titipkan store, bukan nilainya. Store-nya sendiri gak pernah diganti, cuma isinya yang berubah, dan $tema berlangganan isi itu (pelajaran Stores):
<!-- App.svelte -->
<script>
import { setContext } from "svelte"
import { writable } from "svelte/store"
const tema = writable("terang")
setContext("tema", tema)
function gantiTema() {
tema.update((t) => (t === "terang" ? "gelap" : "terang"))
}
</script>
<button on:click={gantiTema}>Ganti tema</button>
<!-- MenuItem.svelte -->
<script>
import { getContext } from "svelte"
export let nama
const tema = getContext("tema")
</script>
<li class={$tema}>{nama}</li>
Hasilnya dari Svelte 4 yang sebenarnya:
| Klik Ganti tema | Tema di App | MenuItem, nilai biasa | MenuItem, store |
|---|---|---|---|
| belum | terang | terang | terang |
| 1 kali | gelap | terang | gelap |
| 2 kali | terang | terang | terang |
Context atau store biasa?
Di pelajaran Stores, keranjang adalah store yang diekspor dari keranjang.js. Semua komponen yang mengimpornya memakai store yang sama: satu nilai untuk seluruh aplikasi. Context berbeda: nilainya cuma berlaku untuk komponen yang menitipkannya dan keturunannya, dan bisa beda per cabang pohon komponen.
Misalnya App memasang dua <Cabang>: <Cabang nama="Pasar" awal="terang" /> dan <Cabang nama="Kampus" awal="gelap" />. Tiap Cabang menjalankan setContext("tema", writable(awal)). MenuItem di cabang Pasar membaca terang, MenuItem di cabang Kampus membaca gelap, dan mengganti tema Pasar gak mengubah Kampus. Kalau keduanya memakai store dari file .js, mengganti satu mengganti semuanya.
Store dari file .js |
Context | |
|---|---|---|
| Jangkauan | seluruh aplikasi | komponen itu dan keturunannya |
| Nilainya | satu untuk semua | bisa beda per cabang |
| Reaktif | ya | cuma kalau isinya store |
| Dibaca dari | file mana pun, kapan pun | keturunan, saat komponen dibuat |
Di SvelteKit ada alasan tambahan: di server, store dari file .js dipakai bersama oleh semua pengunjung, sedangkan context dibuat ulang untuk tiap halaman yang dirender. Data milik satu pengguna lebih aman di context.
Latihan: induk dan anak dalam satu file
Editor latihan cuma menjalankan satu komponen, jadi komponen yang sama memerankan induk dan anak. Itu tetap jalan, karena getContext juga membaca titipan komponen itu sendiri:
<script>
import { setContext, getContext } from "svelte"
setContext("theme", "dark")
const theme = getContext("theme")
</script>
<p>Tema: {theme}</p>
Paragrafnya Tema: dark. Latihan kedua sama bentuknya: setContext("user", { name: "Andi", role: "admin" }), const user = getContext("user"), lalu <p>{user.name} ({user.role})</p> menjadi Andi (admin).
Svelte 5
setContext dan getContext sama di Svelte 5, dan di luar inisialisasi error-nya berbunyi getContext(...) can only be used during component initialisation. Ada dua tambahan. Pertama, objek $state yang dititipkan sudah reaktif tanpa store: const tema = $state({ warna: "terang" }), lalu setContext("tema", tema); ubah tema.warna, jangan ganti objeknya. Kedua, Svelte 5.40 menambah createContext(), yang mengembalikan pasangan function [getTema, setTema], jadi gak ada lagi kunci yang salah ketik.
⚠️ Jebakan yang sering ketemu
getContextdi handler. ErrorFunction called outside component initialization. Baca di awal<script>, simpan diconst.- Mengira nilai biasa ikut berubah. Yang dititipkan adalah nilainya saat itu. Titipkan store kalau keturunannya harus mengikuti perubahan.
- Kunci beda tulisan.
"theme"dan"Theme"adalah dua kunci berbeda; yang dibaca jadiundefined. - Yang dicek latihannya. Kodemu benar-benar memanggil
setContext(dangetContext(, ada<p>, dan teksnya persisTema: dark(latihan pertama) atauAndi (admin)dari<p>{user.name} ({user.role})</p>(latihan kedua), bukan diketik langsung.
TL;DR: Context menitipkan nilai dari induk ke semua keturunannya tanpa prop drilling: setContext("rupiah", rupiah) di App, const rupiah = getContext("rupiah") di MenuItem, dan DaftarMenu di tengahnya gak perlu tahu apa-apa. Dua-duanya diimpor dari "svelte" dan cuma boleh dipanggil saat komponen dibuat; di handler, Svelte 4 melempar Function called outside component initialization. getContext mencari ke atas, yang terdekat menang, kuncinya harus sama persis, dan arahnya cuma ke bawah. Context gak reaktif: yang dititipkan adalah nilai saat itu, jadi untuk tema yang bisa diganti, titipkan writable("terang") dan baca dengan $tema. Store dari file .js berlaku untuk seluruh aplikasi; context berlaku per pohon komponen dan bisa beda per cabang. Di Svelte 5, objek $state di context sudah reaktif, dan createContext() (5.40) memberi pasangan get dan set. Latihannya: setContext("theme", "dark") dan getContext("theme") untuk Tema: dark, lalu objek user untuk Andi (admin).