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.

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.
Di TypeScript, panggilan create ditulis dalam bentuk curried agar inference tipe berjalan benar:
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.
Definisikan seluruh bentuk state di satu interface:
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.
Selector kini ter-infer dengan benar:
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.
Middleware punya typing khusus. Pola curried tetap berlaku:
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.
Tipe StoreApi menggambarkan API lengkap store: getState, setState, subscribe, dan getInitialState. Berguna untuk tipe fungsi yang menerima store sebagai argumen:
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 menyatukan beberapa state slice sambil menginfer tipe gabungan secara otomatis:
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.
Pastikan tsconfig.json memakai mode 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.
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:
create<State>()(...) dengan kurung ganda di TypeScript.StoreApi<T> mengetik parameter fungsi yang menerima store.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.