Episode ini membahas middleware persist yang menyimpan state ke localStorage atau sessionStorage secara otomatis, opsi name untuk kunci penyimpanan, partialize untuk memilih subset state yang disimpan, serta skipHydration untuk kontrol proses rehydrate.

Aplikasi yang baik mengingat preferensi pengguna: theme yang dipilih, item di keranjang, atau langkah terakhir wizard. Zustand menyediakan middleware persist yang menyimpan state ke storage browser secara otomatis dan memulihkannya saat aplikasi dibuka kembali.
Episode 8 membahas persist dari dasar hingga opsi lanjutan: sintaks dasar, opsi name dan partialize, serta skipHydration untuk kontrol penuh proses rehydrate. Persistence yang lebih dalam — custom storage, versioning, dan migrasi — akan kita bahas di episode 11.
Bungkus initializer store dengan 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(initializer, { name: 'theme-storage' }) menyimpan state ke localStorage dengan kunci theme-storage. Setiap set otomatis menulis ulang storage; saat store pertama kali dibuat, nilai tersimpan di-rehydrate kembali.
Saat aplikasi dimuat, persist membaca storage, men-deserialize, lalu menggabungkannya ke state awal. Di UI, rehydrate berjalan hampir tanpa terasa untuk storage sinkron seperti localStorage — perbedaan singkat hanya terlihat di aplikasi SSR, topik episode 14.
name menentukan kunci di storage. Gunakan nama yang deskriptif dan unik per store:
export const useCart = create<CartState>()(
persist(
(set) => ({
items: [],
discount: 0,
addItem: (item) => set((s) => ({ items: [...s.items, item] })),
}),
{
name: 'cart-persist',
partialize: (state) => ({ items: state.items }),
},
),
)partialize: (state) => ({ items: state.items }) memilih subset yang disimpan — di contoh ini hanya items, bukan discount atau fungsi action. Persist tidak pernah menyimpan fungsi; partialize menyaring data sekaligus menghindari field yang tidak perlu.
Ada dua alasan: keamanan dan ukuran. Jangan pernah mempersist data sensitif seperti token — dan jangan menyimpan state besar yang bisa dibangun ulang. Fungsi action otomatis dibuang persist, tapi field data yang besar tetap memakan ruang storage.
Secara default persist langsung me-rehydrate saat store dibuat. skipHydration: true menunda proses itu sampai kalian memanggil rehydrate() secara manual:
export const useSettings = create<SettingsState>()(
persist(
(set) => ({ language: 'id', notifications: true }),
{ name: 'settings', skipHydration: true },
),
)
export async function rehydrateSettings() {
await useSettings.persist.rehydrate()
}skipHydration: true menahan rehydrate, lalu useSettings.persist.rehydrate() menjalankannya kapan pun dibutuhkan. Ini pola penting untuk aplikasi SSR: render awal memakai state default, lalu rehydrate terjadi setelah browser siap — detailnya di episode 14.
onRehydrateStorage memberi hook sebelum dan sesudah proses rehydrate:
persist(
(set) => ({ user: null, token: null }),
{
name: 'session',
onRehydrateStorage: () => (state) => {
console.log('hydration selesai', state)
},
},
)onRehydrateStorage: () => (state) => ... menerima callback yang dipanggil setelah rehydrate selesai. Cocok untuk sinkronisasi data ter-migrasi atau menandai aplikasi siap digunakan.
Persist paling berguna untuk state yang harus bertahan antar sesi: theme, preferensi bahasa, keranjang belanja, draft form, dan data yang tidak bergantung server. Sebaliknya, jangan persist state yang bersifat sementara — loading status, error, atau data yang selalu bisa di-refetch.
Warning
Jangan pernah menyimpan token akses atau secret ke localStorage via persist. Storage ini rentan XSS. Pola keamanan yang benar kita bahas di episode 15.
Episode 8 mengenalkan persist sebagai middleware penjaga state lintas sesi: penyimpanan otomatis ke localStorage, opsi name untuk kunci storage, partialize untuk memilih subset, dan skipHydration untuk kontrol penuh proses rehydrate.
Inti yang harus dibawa pulang:
Di episode 9 selanjutnya kita akan membahas middleware: immer — memperbarui state nested secara mutable-tapi-immutable lewat fungsi draft, membandingkannya dengan manual spread, serta trade-off performa dalam memilih keduanya.