Context Solid — SolidJS

Context membagikan data ke seluruh subtree tanpa prop drilling. createContext import { createContext, useContext } from "solid-js" const ThemeContext =…

Context membagikan satu nilai ke semua komponen di bawah sebuah Provider, tanpa mengopernya lewat props dari level ke level. createContext(nilaiBawaan) membuat wadahnya, <X.Provider value={...}> menyediakan nilainya untuk semua yang ada di dalamnya, dan useContext(X) membacanya dari Provider terdekat di atas. Bayangin Toko Buku Kak Nadia punya cabang di Kuala Lumpur. Label harga di rak gak diberi tahu mata uangnya satu per satu. Tiap label membaca papan di dinding toko tempat dia digantung: di toko Jakarta tertulis Rp, di cabang Kuala Lumpur tertulis RM. Label yang dicetak di brosur, di luar toko mana pun, memakai mata uang yang tercetak di labelnya sendiri, yaitu nilai bawaannya.

🧪 Tebakan cepat: Header ditaruh di luar <TokoContext.Provider>, dan kartu-kartu buku di dalamnya. Provider-nya membawa signal tema. Kamu klik Ganti tema dan semua kartu jadi gelap. Header ikut gelap atau gak? Jawabannya ada di bagian "Di luar Provider: nilai bawaan".

createContext, Provider, useContext

import { createContext, useContext } from "solid-js"

const ThemeContext = createContext("light")

function ThemedText() {
  const theme = useContext(ThemeContext)
  return <p>Tema: {theme}</p>
}

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <ThemedText />
    </ThemeContext.Provider>
  )
}

Hasilnya Tema: dark.

Di luar Provider: nilai bawaan

Toko Buku Kak Nadia dengan mata uang dan tema di context:

import { createContext, createSignal, useContext } from "solid-js"

const TokoContext = createContext({ mataUang: "Rp", tema: () => "terang" })

function Header() {
  const toko = useContext(TokoContext)
  return <header class={toko.tema()}>Toko Buku Kak Nadia · harga dalam {toko.mataUang}</header>
}

function KartuBuku(props) {
  const toko = useContext(TokoContext)
  return (
    <div class={"kartu " + toko.tema()}>
      <h3>{props.judul}</h3>
      <p>{toko.mataUang}{props.harga}</p>
    </div>
  )
}

function App() {
  const [tema, setTema] = createSignal("terang")
  const ganti = () => setTema(t => (t === "terang" ? "gelap" : "terang"))

  return (
    <div>
      <Header />
      <TokoContext.Provider value={{ mataUang: "Rp", tema }}>
        <button onClick={ganti}>Ganti tema</button>
        <KartuBuku judul="Laskar Pelangi" harga={89000} />
        <KartuBuku judul="Bumi Manusia" harga={120000} />
        <TokoContext.Provider value={{ mataUang: "RM", tema }}>
          <KartuBuku judul="Laskar Pelangi (cabang KL)" harga={25} />
        </TokoContext.Provider>
      </TokoContext.Provider>
    </div>
  )
}

Diuji di Solid 1.9, sesudah Ganti tema diklik:

Komponen useContext dapat dari Tampil
Header gak ada Provider di atasnya: nilai bawaan harga dalam Rp, kelas terang
Kartu Laskar Pelangi, Bumi Manusia Provider luar Rp89000, Rp120000, kelas kartu gelap
Kartu cabang KL Provider dalam, yang terdekat RM25, kelas kartu gelap

Header tetap terang. Itu jawaban tebakannya. Dia gak berada di dalam Provider mana pun, jadi useContext mengembalikan nilai bawaan dari createContext, yang temanya selalu "terang". Context cuma sampai ke komponen di dalam Provider-nya. Kalau context dibuat tanpa nilai bawaan, createContext(), komponen di luar Provider mendapat undefined, dan toko.mataUang berhenti dengan TypeError: Cannot read properties of undefined (reading 'mataUang'). Kartu cabang KL ada di dalam dua Provider, dan yang terdekat menang: RM25.

Nilai yang bisa berubah

Provider membaca value sekali, saat dibuat. Supaya pembacanya ikut berubah, kirim signal-nya, bukan hasil bacaannya:

value Pembacanya menulis Saat tema diganti
{{ mataUang: "Rp", tema }} toko.tema() kartu ikut gelap
{{ mataUang: "Rp", tema: tema() }} toko.tema tetap terang, karena yang dikirim cuma string "terang"

Latihan kedua memakai pola yang sama untuk nama pengguna:

import { createContext, useContext, createSignal } from "solid-js"

const UserContext = createContext()

function UserDisplay() {
  const ctx = useContext(UserContext)
  return <p>User: {ctx.name()}</p>
}

function App() {
  const [name, setName] = createSignal("Andi")

  return (
    <UserContext.Provider value={{ name, setName }}>
      <UserDisplay />
    </UserContext.Provider>
  )
}

Kapan pakai context

Untuk nilai yang dibaca banyak komponen di berbagai kedalaman: tema, mata uang, bahasa, pengguna yang sedang login. Kalau cuma satu anak yang butuh, props lebih jelas. Satu lagi: useContext dipanggil saat komponen jalan. Dipanggil belakangan, misalnya di dalam setTimeout, dia gak tahu lagi posisinya di pohon komponen dan mengembalikan nilai bawaan.

⚠️ Jebakan yang sering ketemu

TL;DR: const ThemeContext = createContext("light") dibuat sekali di luar komponen. <ThemeContext.Provider value="dark"> menyediakan nilai untuk semua yang ada di dalamnya, dan useContext(ThemeContext) membacanya dari Provider terdekat di atas: Tema: dark. Di luar Provider, useContext mengembalikan nilai bawaan dari createContext (atau undefined kalau gak ada). Provider bertingkat: yang terdekat menang. Provider membaca value sekali, jadi kirim signal-nya (value={{ name, setName }}) dan baca dengan kurung (ctx.name()) supaya tampilannya ikut berubah.

Yang akan kamu pelajari