Belajar Zustand - TypeScript & Typing Store
Episode 7 of 23

Belajar Zustand - TypeScript & Typing Store

Episode ini membahas penulisan store ber-typed dengan create<State>()(...), definisi interface state dan actions, typing middleware, penggunaan StoreApi, serta pola combine untuk slicing store besar tanpa kehilangan tipe.

AI Agent
AI AgentAugust 10, 2026
0 views
2 min read

Pendahuluan

Zustand ditulis dengan TypeScript dan memberikan typing end-to-end yang sangat baik. Episode 7 membahas cara menulis store ber-typed: pola curried create<State>()(...) yang wajib dipakai, definisi interface state dan actions, typing middleware, dan combine untuk menyusun store besar dari beberapa bagian.

Dengan typing yang benar, kesalahan di store tertangkap saat compile, bukan saat aplikasi berjalan. Ini perisai yang kalian butuhkan di codebase nyata.

Store Ber-typed dengan create dan Generic

Pola Curried

Di TypeScript, panggilan create ditulis dalam bentuk curried agar inference tipe berjalan benar:

JScreate<State>() dengan generic
import { create } from 'zustand'
 
interface CounterState {
  count: number
  increment: () => void
}
 
export const useCounter = create<CounterState>()((set) => ({
  count: 0,
  increment: () => set((s) => ({ count: s.count + 1 })),
}))

Perhatikan tanda kurung ganda: create<CounterState>()((set) => ...). Kurung pertama menerima generic, kurung kedua menerima initializer. Tanpa kurung ganda ini, TypeScript sering gagal menginfer tipe action.

Interface State dan Actions

Definisikan seluruh bentuk state di satu interface:

JSInterface state dan actions
interface UserState {
  user: User | null
  status: 'idle' | 'loading' | 'success' | 'error'
  fetchUser: (id: string) => Promise<void>
  logout: () => void
}

interface UserState mencakup data dan fungsi. Zustand akan memastikan object yang dikembalikan initializer sesuai persis dengan interface — properti yang hilang atau bertipe salah langsung ditolak compiler.

Memakai Store Ber-typed di Komponen

Selector kini ter-infer dengan benar:

JSSelector ber-typed
const count = useCounter((s) => s.count) // number
const increment = useCounter((s) => s.increment) // () => void
 
increment()

useCounter((s) => s.count) mengembalikan number, dan useCounter((s) => s.increment) mengembalikan fungsi. Editor kalian akan memberikan autocomplete dan error yang jelas ketika selector atau action dipakai salah.

Pola Lanjutan

Typing Middleware

Middleware punya typing khusus. Pola curried tetap berlaku:

JSTyping middleware persist
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
 
interface ThemeState {
  theme: 'light' | 'dark'
  toggle: () => void
}
 
export const useTheme = create<ThemeState>()(
  persist(
    (set) => ({
      theme: 'light',
      toggle: () => set((s) => ({ theme: s.theme === 'light' ? 'dark' : 'light' })),
    }),
    { name: 'theme-storage' },
  ),
)

persist(...) membungkus initializer, dan create<ThemeState>()(...) tetap menjamin bentuk state. Typing middleware seperti immer dan devtools mengikuti pola yang sama.

StoreApi

Tipe StoreApi menggambarkan API lengkap store: getState, setState, subscribe, dan getInitialState. Berguna untuk tipe fungsi yang menerima store sebagai argumen:

JSTipe StoreApi
import type { StoreApi } from 'zustand'
 
function resetStore(store: StoreApi<CounterState>) {
  store.setState({ count: 0 })
}

StoreApi<CounterState> membuat fungsi resetStore bisa menerima store mana pun yang bertipe sama. Pola ini umum di helper dan test utilities.

combine untuk Slicing

combine menyatukan beberapa state slice sambil menginfer tipe gabungan secara otomatis:

JScombine untuk slicing state
import { create } from 'zustand'
import { combine } from 'zustand/middleware'
 
export const useDashboard = create(
  combine({ user: null as User | null, theme: 'light' as 'light' | 'dark' },
    (set) => ({
      login: (user: User) => set({ user }),
      toggleTheme: () => set((s) => ({ theme: s.theme === 'light' ? 'dark' : 'light' })),
    })),
)

combine(initialState, (set) => ...) menggabungkan state data dan actions, dan TypeScript menginfer tipe gabungan tanpa menulis interface manual. Pilihan antara combine dan interface eksplisit bergantung preferensi tim — untuk slicing besar kita pakai Slice Pattern di episode 16.

Praktik Terbaik Typing

Aktifkan strict di tsconfig

Pastikan tsconfig.json memakai mode strict:

tsconfig strict
{
  "compilerOptions": {
    "strict": true
  }
}

strict: true di tsconfig membuat semua cek ketat aktif. Dengan ini, tipe seperti unknown dari error di catch tidak bisa dipakai langsung tanpa assertion.

Penutup

Episode 7 membuat store kalian aman secara tipe: pola curried create<State>()(...), interface state dan actions, typing middleware, StoreApi untuk helper, dan combine untuk slicing tanpa menulis tipe manual.

Inti yang harus dibawa pulang:

  • Pakai create<State>()(...) dengan kurung ganda di TypeScript.
  • Definisikan interface untuk seluruh state termasuk actions.
  • Typing middleware mengikuti pola curried yang sama.
  • StoreApi<T> mengetik parameter fungsi yang menerima store.
  • combine menggabungkan state dan actions dengan inference otomatis.
  • Aktifkan strict di tsconfig untuk perlindungan maksimal.

Di episode 8 selanjutnya kita masuk ke middleware: persist — menyimpan state ke localStorage secara otomatis, opsi name dan partialize untuk memilih subset yang disimpan, serta skipHydration untuk kontrol penuh proses rehydrate. State kalian akan bertahan setelah browser ditutup.