Episode ini membahas zustand/vanilla: membuat store tanpa hook dengan createStore, memakai getState dan subscribe di kode non-React seperti service Node dan web worker, lalu menghubungkan store vanilla kembali ke React lewat useStore. Satu store bisa dipakai dari dua dunia dengan satu sumber kebenaran.

Salah satu keunggulan arsitektur Zustand adalah intinya yang tidak bergantung pada React. Episode 17 membahas zustand/vanilla: membuat store tanpa hook, memakai getState dan subscribe di kode non-React seperti service Node atau web worker, lalu menghubungkan store vanilla ke komponen React lewat useStore.
Kalian akan melihat mengapa satu store bisa dipakai dari dua dunia yang berbeda dengan satu sumber kebenaran.
Modul zustand/vanilla mengekspos createStore yang mengembalikan store API, bukan hook:
import { createStore } from 'zustand/vanilla'
interface CounterState {
count: number
increment: () => void
}
export const counterStore = createStore<CounterState>()((set) => ({
count: 0,
increment: () => set((s) => ({ count: s.count + 1 })),
}))
counterStore.getState()
counterStore.subscribe((s) => console.log('count:', s.count))createStore dari zustand/vanilla mengembalikan objek dengan getState, setState, subscribe, dan getInitialState. Tidak ada JSX, tidak ada hook, tidak ada React di dalamnya — murni state container yang bisa diimpor di mana saja.
Karena tidak memakai React, store vanilla bisa dipakai di service Node yang mengolah antrean atau event. Contoh listener yang merespons perubahan state dari luar:
import { counterStore } from './counter-store.js'
const unsubscribe = counterStore.subscribe((state) => {
if (state.count > 10) {
notifyAdmin('counter melewati batas')
}
})
process.on('SIGINT', unsubscribe)counterStore.subscribe(listener) memanggil listener dengan state utuh setiap kali berubah. Ini bentuk event bus yang sederhana: logic bisnis membaca state dan mengambil keputusan tanpa perlu UI.
Di web worker, state yang dipegang worker tidak bisa berbagi otomatis dengan thread utama. Store vanilla di dalam worker menjadi sumber kebenaran lokal:
import { createStore } from 'zustand/vanilla'
const workerStore = createStore((set) => ({
total: 0,
add: (n) => set((s) => ({ total: s.total + n })),
}))
self.onmessage = (event) => {
workerStore.getState().add(event.data)
self.postMessage({ total: workerStore.getState().total })
}workerStore.getState().add(event.data) memutasi state di worker, lalu hasilnya dikirim kembali ke thread utama. Store vanilla menghilangkan kebutuhan mengelola mutable global secara manual di worker.
Store vanilla bisa langsung dipakai di React dengan useStore dari zustand. Komponen me-subscribe ke store yang sama sehingga re-render selektif tetap bekerja:
import { useStore } from 'zustand'
import { counterStore } from './counter-store'
export function Counter() {
const count = useStore(counterStore, (s) => s.count)
return (
<div>
<p>Count: {count}</p>
<button onClick={() => counterStore.getState().increment()}>
Tambah
</button>
</div>
)
}useStore(counterStore, (s) => s.count) mengambil store vanilla dan selector seperti hook biasa. Hasilnya: logic state di file terpisah yang bisa diuji tanpa React, dan UI React yang me-subscribe dengan performa penuh — satu store, dua pemakai.
Episode 17 membuka pintu Zustand keluar dari React: createStore menghasilkan store vanilla yang dipakai di Node service dan web worker lewat getState dan subscribe, lalu dihubungkan kembali ke React dengan useStore tanpa menulis ulang store.
Inti yang harus dibawa pulang:
Di episode 18 selanjutnya kita akan membahas testing store dan middleware — menulis unit test dengan Vitest untuk set dan get state, menguji action async dan middleware persist dengan storage mock, serta component testing dengan React Testing Library.