Episode ini membedakan server state dan client state, lalu menyusun aturan kapan data API masuk ke Zustand dan kapan ke TanStack Query atau SWR. Kalian juga belajar pola hybrid yang memisahkan cache server dari store global dengan contoh auth dan user profile.

Sebuah aplikasi modern tidak hanya menyimpan state UI di memori, tetapi juga berkomunikasi dengan API untuk mengambil data pengguna, daftar produk, dan lainnya. Pertanyaan yang sering muncul: data dari server itu masuk ke Zustand atau ke library cache seperti TanStack Query? Episode 12 menjawab pertanyaan itu dengan membedakan dua kategori state yang berbeda dan menyusun pola hybrid yang dipakai tim production.
Kalian akan memahami perbedaan server state dan client state, kriteria kapan data API masuk ke Zustand, kapan memakai TanStack Query atau SWR, lalu menyusun contoh auth dan user profile dengan pola hybrid.
Server state adalah data yang dimiliki server dan disinkronkan lewat API: profil pengguna, daftar post, saldo akun. Client state adalah data yang lahir dan hidup di browser: status modal, nilai form, tema aktif, atau current page.
Perbedaan ini penting karena menentukan di mana data hidup. Server state butuh cache, retry, dan invalidasi ketika data berubah di server. Client state butuh kecepatan dan tidak perlu sinkron dengan server. Mencampur keduanya dalam satu store menyebabkan kode susah diprediksi: store ikut menangani loading, error, caching, dan refetch sekaligus.
Ada beberapa situasi di mana menyimpan data API di Zustand masuk akal:
Contoh paling umum adalah token dan profil sesi pengguna. Token harus tersedia secara sinkron untuk setiap request, dan sebaiknya tidak berubah-ubah:
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface AuthState {
token: string | null
user: User | null
setSession: (token: string, user: User) => void
clearSession: () => void
}
export const useAuth = create<AuthState>()(
persist(
(set) => ({
token: null,
user: null,
setSession: (token, user) => set({ token, user }),
clearSession: () => set({ token: null, user: null }),
}),
{ name: 'auth-session' },
),
)setSession(token, user) menyimpan data sesi sekali per login, dan komponen mana pun bisa membaca token secara sinkron tanpa menunggu response ulang dari API.
Ketika data sering berubah di server dan dibaca ulang berkali-kali, gunakan library server cache. TanStack Query dan SWR menyediakan caching, deduplication request, retry, refetch on window focus, dan invalidasi query — fitur yang tidak ada di Zustand.
Instal TanStack Query di project kalian:
npm i @tanstack/react-queryLalu definisikan query dengan queryKey dan queryFn:
import { useQuery } from '@tanstack/react-query'
export function useUserProfile(userId: string) {
return useQuery({
queryKey: ['user', userId],
queryFn: () =>
fetch(`/api/users/${userId}`).then((res) => res.json()),
staleTime: 60_000,
})
}queryKey: ['user', userId] membuat cache per pengguna, dan staleTime 60 detik mencegah refetch yang berlebihan.
Kombinasi paling sehat: Zustand menyimpan state client global seperti token dan preferensi, TanStack Query menyimpan cache server. Ketika user berubah, invalidasi query di TanStack Query dan update token di Zustand:
async function login(email: string, password: string) {
const res = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
})
const data = await res.json()
useAuth.getState().setSession(data.token, data.user)
await queryClient.invalidateQueries({ queryKey: ['user'] })
}useAuth.getState().setSession(...) mengupdate store global, sementara invalidateQueries memaksa TanStack Query mengambil ulang profil terkait. Dengan pola ini, store tidak pernah menyimpan cache server, dan server cache tidak pernah menangani aksi global.
Episode 12 memisahkan dua dunia state: server state yang hidup di TanStack Query atau SWR, dan client state yang hidup di Zustand. Data sesi dan UI masuk ke Zustand; data yang berubah di server dan dibaca berkali-kali masuk ke cache query dengan invalidasi.
Inti yang harus dibawa pulang:
Di episode 13 selanjutnya kita akan membahas performance dan transient updates — mengontrol re-render dengan selector dan useShallow, menghindari object baru setiap render, serta memperbarui state seperti progress bar tanpa memicu render.