Custom Primitives — SolidJS

Membangun custom reactive primitives yang reusable — kekuatan utama Solid. Custom Hook Pattern function createCounter(initial = 0) { const [count, setCount] =…

Custom primitive adalah fungsi biasa yang kamu tulis sendiri: di dalamnya dia membuat signal (kadang juga efek dan onCleanup), lalu mengembalikan pembaca nilainya beserta cara mengubahnya. Namanya diawali create, seperti createSignal milik Solid. Dengan begitu logika yang sama bisa dipakai banyak komponen tanpa ditulis ulang. Bayangin cetakan kartu stok di Toko Buku Kak Nadia. Tiap rak memakai cetakan yang sama, tapi tiap kartu yang dicetak mencatat angkanya sendiri.

🧪 Tebakan cepat: kartu Laskar Pelangi dan kartu Bumi Manusia sama-sama memanggil createCounter(0) untuk jumlah pesanannya. Kamu menekan + dua kali di Laskar Pelangi. Jumlah di Bumi Manusia jadi berapa? Jawabannya ada di bagian "Tiap panggilan, signal baru".

createCounter: logika yang dipakai ulang

import { createSignal } from "solid-js"

function createCounter(initial = 0) {
  const [count, setCount] = createSignal(initial)

  return {
    count,
    increment: () => setCount((c) => c + 1),
    decrement: () => setCount((c) => c - 1),
    reset: () => setCount(initial),
  }
}

function App() {
  const counter = createCounter(0)

  return (
    <div>
      <p>Count: {counter.count()}</p>
      <button onClick={counter.increment}>+</button>
      <button onClick={counter.decrement}>-</button>
      <button onClick={counter.reset}>Reset</button>
    </div>
  )
}
Mulai dari Count: 0, klik Paragraf
+ Count: 1
- Count: -1
+, lalu Reset Count: 0

Tiap panggilan, signal baru

function KartuBuku(props) {
  const jumlah = createCounter(0)
  return (
    <div class="kartu">
      <h3>{props.judul}</h3>
      <p>Jumlah: {jumlah.count()}</p>
      <button onClick={jumlah.increment}>+</button>
    </div>
  )
}

Dua klik + di Laskar Pelangi, dan Bumi Manusia tetap 0. Itu jawaban tebakannya. Tiap KartuBuku memanggil createCounter(0) sendiri, dan tiap panggilan menjalankan createSignal baru. Kalau createSignal ditulis di luar fungsinya, semua kartu berbagi satu signal: klik di Laskar Pelangi ikut mengubah Bumi Manusia.

createToggle: mengembalikan pasangan

Latihan kedua mengembalikan array, seperti createSignal, supaya pemakainya bebas memberi nama:

function createToggle(initial = false) {
  const [value, setValue] = createSignal(initial)
  const toggle = () => setValue((v) => !v)
  return [value, toggle]
}

function App() {
  const [visible, toggle] = createToggle(false)

  return (
    <div>
      <p>{visible() ? "Visible" : "Hidden"}</p>
      <button onClick={toggle}>Toggle</button>
    </div>
  )
}

Awalnya Hidden, sekali klik Visible, dua kali klik Hidden lagi. setValue((v) => !v) membalik nilai lamanya.

createDebounced: primitive yang membersihkan dirinya

Kotak cari di toko menyaring katalog. Menyaring tiap huruf itu boros, jadi tunggu pengetiknya berhenti 300 ms:

import { createSignal, createEffect, onCleanup } from "solid-js"

function createDebounced(sumber, jeda) {
  const [nilai, setNilai] = createSignal(sumber())
  createEffect(() => {
    const baru = sumber()
    const id = setTimeout(() => setNilai(baru), jeda)
    onCleanup(() => clearTimeout(id))
  })
  return nilai
}

// di komponen Pencarian:
const [cari, setCari] = createSignal("")
const cariPelan = createDebounced(cari, 300)

Diuji di Solid 1.9, mengetik bumi satu huruf tiap 100 ms:

createDebounced Filter yang membaca cariPelan() jalan
dengan onCleanup 1 kali, dengan "bumi", 300 ms sesudah huruf terakhir
tanpa onCleanup 4 kali: "b", "bu", "bum", "bumi"

createLocalStorage: nilai yang bertahan

function createLocalStorage(kunci, awal) {
  const tersimpan = localStorage.getItem(kunci)
  const [nilai, setNilai] = createSignal(tersimpan !== null ? JSON.parse(tersimpan) : awal)
  createEffect(() => localStorage.setItem(kunci, JSON.stringify(nilai())))
  return [nilai, setNilai]
}

const [keranjang, setKeranjang] = createLocalStorage("keranjang", [])

Tiap kali keranjang berubah, efeknya menyimpan salinan JSON ke browser. Saat halaman dibuka lagi, nilai awalnya dibaca dari sana, jadi isi keranjang gak hilang.

Aturan menulis primitive

⚠️ Jebakan yang sering ketemu

TL;DR: Custom primitive adalah fungsi createXxx yang membuat signal di dalamnya dan mengembalikan pembacanya plus cara mengubahnya: createCounter(0) memberi { count, increment, decrement, reset }, createToggle(false) memberi [value, toggle]. Tiap panggilan membuat signal baru, jadi dua kartu gak saling ganggu. Kembalikan fungsinya (value), bukan nilainya (value()). Primitive yang menyalakan sesuatu, seperti timer di createDebounced, mematikannya dengan onCleanup: tiap huruf baru membatalkan timer lama, dan gak ada timer yang tersisa saat komponennya dibongkar. Keuntungannya: reusable dan testable.

Yang akan kamu pelajari