Kadang kamu butuh elemen DOM aslinya di tangan: untuk memberi fokus ke kotak cari, menyeleksi isinya, atau mengukur lebarnya. Itu gunanya ref: atribut ref di JSX menyerahkan elemen itu ke kodemu. Directive selangkah lebih jauh: perilaku untuk elemen yang ditulis sekali sebagai fungsi, lalu dipasang ke elemen mana pun dengan use:namaFungsi. Bayangin rak di Toko Buku Kak Nadia. Ref itu seperti memegang langsung buku di rak, bukan cuma tahu judulnya. Directive itu seperti stiker aturan yang bisa ditempel di laci mana pun: "kalau ada yang menyentuh di luar laci, tutup lacinya".
🧪 Tebakan cepat: kotak cari ditulis
<input ref={(el) => el.focus()} placeholder="Cari judul" />. Saat halaman tampil, kursornya masuk ke kotak itu atau gak? Jawabannya ada di bagian "Callback ref: dipanggil saat elemennya baru dibuat".
ref ke variabel: let kotak
import { onMount } from "solid-js"
function Cari() {
let kotak
console.log("badan:", kotak)
onMount(() => {
kotak.focus()
})
return <input ref={kotak} placeholder="Cari judul" />
}
Diuji di Solid 1.9.17:
| Saat | Isi kotak |
Sudah di halaman? |
|---|---|---|
Badan komponen, console.log |
undefined |
belum ada elemen |
return: <input> dibuat |
elemen <input> |
belum |
onMount |
elemen <input> |
sudah, jadi focus() berhasil |
let kotakdibuat kosong. Compiler Solid mengubahref={kotak}jadikotak = elemennya, tepat saat<input>dibuat. Karena diisi ulang, tulislet, bukanconst.- Gak ada
.currentsepertiuseRefdi React.kotaklangsung elemen DOM biasa, lengkap denganfocus(),select(), dangetBoundingClientRect(). - Baca ref-nya di
onMountatau di event handler. Di badan komponen dia masihundefined, dankotak.focus()di sana berhenti denganTypeError: Cannot read properties of undefined (reading 'focus').
Callback ref: dipanggil saat elemennya baru dibuat
ref juga menerima fungsi. Solid memanggilnya dengan elemennya, saat elemen itu baru dibuat, sebelum masuk ke halaman. Diuji di Solid 1.9.17: di dalam callback, el.isConnected bernilai false, dan el.focus() gak berbuat apa-apa, karena elemen di luar halaman gak bisa memegang fokus. Jadi kursornya gak masuk. Itu jawaban tebakannya.
Supaya berhasil, tunggu sampai elemennya terpasang:
// latihan pertama: tunggu onMount di dalam callback
<input ref={(el) => onMount(() => el.focus())} placeholder="Auto focus" />
// latihan kedua: tunda satu putaran, lalu fokus dan seleksi isinya
<input value="Select me!" ref={(el) => setTimeout(() => { el.focus(); el.select() }, 0)} />
onMountyang dipanggil di dalam callback ref tetap milik komponennya, jadi jalan sesudah halamannya terpasang.setTimeout(..., 0)menunda kodenya sampai Solid selesai memasang halaman.el.select()menyorot teksSelect me!, danel.focus()memastikan kotaknya memegang fokus, karenaselect()saja gak selalu memindahkan fokus.- Callback ref juga cara menyimpan elemen ke signal:
ref={setKotak}.
use: directive, perilaku yang bisa dipakai ulang
Laci keranjang di toko harus tertutup kalau pembeli mengklik di luar laci. Perilaku itu ditulis sekali sebagai directive clickOutside:
import { createSignal, onCleanup, Show } from "solid-js"
function clickOutside(el, accessor) {
const onClick = (e) => {
if (!el.contains(e.target)) accessor()()
}
document.addEventListener("click", onClick)
onCleanup(() => document.removeEventListener("click", onClick))
}
function App() {
const [buka, setBuka] = createSignal(false)
return (
<div>
<button onClick={() => setBuka(true)}>Keranjang (2)</button>
<Show when={buka()}>
<aside class="laci" use:clickOutside={() => setBuka(false)}>
<p>Laskar Pelangi · Rp89.000</p>
<p>Bumi Manusia · Rp120.000</p>
</aside>
</Show>
</div>
)
}
use:clickOutside={nilai}membuat Solid memanggilclickOutside(el, () => nilai)saat<aside>dibuat.eladalah elemennya,accessoradalah fungsi yang mengembalikan nilainya.- Nilainya di sini juga fungsi,
() => setBuka(false). Jadiaccessor()mengembalikan fungsi itu, danaccessor()()memanggilnya. Denganuse:tooltip="Stok habis",accessor()mengembalikan teks"Stok habis". el.contains(e.target)memeriksa apakah yang diklik ada di dalam laci. Kalau gak, laci ditutup.onCleanupdi dalam directive jalan saat<aside>hilang, waktuShowmenutupnya, jadi listener-nya ikut dilepas.- Diuji di Solid 1.9.17: klik
Keranjang (2)membuka laci (listener yang baru dipasang gak ikut mendengar klik itu), klik di dalam laci gak menutupnya, klik di luar menutupnya. clickOutsideharus berupa fungsi dengan nama yang sama di file itu, ditulis di sana atau diimpor.
💡 TypeScript: daftarkan directive-nya
Di TypeScript, use:clickOutside baru dikenali setelah directive-nya didaftarkan:
declare module "solid-js" {
namespace JSX {
interface Directives {
clickOutside: () => void
}
}
}
Tipe di sebelah nama directive adalah tipe nilainya, di sini fungsi tanpa argumen. Kalau clickOutside diimpor dari file lain dan cuma dipakai lewat use:, TypeScript bisa menganggap impornya gak terpakai lalu membuangnya. Pastikan namanya juga terpakai di kode biasa.
⚠️ Jebakan yang sering ketemu
- Membaca ref di badan komponen. Di sana dia masih
undefined. Pakai dionMountatau di event handler. el.focus()langsung di callback ref. Elemennya belum di halaman, jadi gak terjadi apa-apa. Bungkus denganonMount(() => ...)atausetTimeout(() => ..., 0).- Directive tanpa
onCleanup. Listener lama tetap terpasang didocument. Diuji di Solid 1.9.17: sesudah laci ditutup, klikKeranjang (2)berikutnya membuka laci, lalu listener lama langsung menutupnya lagi. Lacinya gak bisa dibuka. - Lupa
select(). Fokus saja menaruh kursor di kotaknya; isinya baru tersorot sesudahel.select(). Latihan kedua memeriksa callback ref-nya danselect().
TL;DR: let kotak plus <input ref={kotak} /> mengisi kotak dengan elemennya saat dibuat. Pakai di onMount(() => kotak.focus()), karena di badan komponen dia masih undefined. Callback ref ref={(el) => ...} dipanggil saat elemennya dibuat, sebelum masuk ke halaman, jadi el.focus() di sana gak berhasil: tunggu dengan onMount(() => el.focus()) atau setTimeout(() => { el.focus(); el.select() }, 0). Directive use:clickOutside={() => setBuka(false)} memanggil clickOutside(el, accessor), dengan accessor() sebagai nilainya, dan onCleanup di dalamnya melepas listener saat elemennya hilang.