Server state punya karakteristik berbeda dari state lokal. Episode ini membahas Pinia Colada untuk fetching, caching, dan deduplication, lalu integrasi store dengan Vue Router untuk membaca route params dan guard, serta pemakaian composables eksternal bersama Pinia.

Sejak episode 7, action async kita menyimpan hasil fetch di state. Pola itu berfungsi, tetapi server state — data yang datang dari API — punya kebutuhan khusus: caching, deduplication, retry, dan invalidation. Menangani semua itu manual adalah jalan menuju bug.
Episode 14 membahas solusi resmi di ekosistem Pinia untuk masalah ini: Pinia Colada untuk async server state, integrasi store dengan Vue Router, dan cara menggabungkan composables eksternal dengan store.
Pinia Colada adalah library resmi di ekosistem Pinia untuk server state. Ia menangani fetching, caching, dan deduplication dengan key yang jelas:
import { defineQuery } from '@pinia/colada'
export const useUserQuery = defineQuery({
key: ['user', 'profile'],
query: () => fetch('/api/profile').then((res) => res.json()),
})defineQuery({ key, query }) mendefinisikan query yang di-cache berdasarkan key. Di komponen, hasilnya bisa dibaca dengan state data, status, dan method refetch:
<script setup lang="ts">
const { data, status, refetch } = useUserQuery()
</script>
<template>
<div v-if="status === 'loading'">Memuat...</div>
<div v-else>{{ data?.name }}</div>
</template>Keunggulannya dibanding action manual: dua komponen yang memakai query sama akan berbagi satu cache dan tidak melakukan fetch ganda — inilah deduplication yang sulit ditiru manual.
Pedoman sederhana yang dipakai tim production:
useUserQuery() // server state: cache dan dedup
useCartStore() // client state: interaksi userCampuran keduanya umum: action di store menyebut refetch dari query untuk menyinkronkan setelah mutasi, atau query membaca state store sebagai parameter.
Store sering membutuhkan informasi route. Di dalam setup, gunakan useRoute; di luar setup, import instance router:
import router from '@/router'
export const useDetailStore = defineStore('detail', {
actions: {
async loadCurrent() {
const id = router.currentRoute.value.params.id
this.current = await fetch(`/api/item/${id}`).then((r) => r.json())
},
},
})router.currentRoute.value.params.id membaca parameter route langsung dari instance router. Alternatif yang lebih idiomatis: baca id lewat useRoute() di komponen lalu kirim sebagai argumen action.
Store dan composables bukan rival — keduanya bisa saling melengkapi:
import { useLocalStorage } from '@vueuse/core'
export const usePrefsStore = defineStore('prefs', () => {
const fontSize = useLocalStorage('font-size', 16)
function setSize(size: number) {
fontSize.value = size
}
return { fontSize, setSize }
})useLocalStorage('font-size', 16) di dalam Setup store menggabungkan composable VueUse dengan state global. Pola ini hanya bisa dilakukan dengan Setup store, seperti yang dibahas di episode 4.
Info
Keputusan penting: jangan menyimpan seluruh server state ke dalam Pinia. Gunakan Colada atau Vue Query untuk lapisan itu, dan Pinia untuk state client — pembagian ini menjaga arsitektur tetap sehat di skala besar.
Episode 14 memperluas Pinia ke arsitektur modern. Kalian sekarang memahami Pinia Colada untuk async server state dengan caching dan dedup, cara menggabungkan store dengan Vue Router, serta integrasi composables eksternal di dalam Setup store.
Inti yang harus dibawa pulang:
useRoute() atau instance router.Di episode 15 selanjutnya kita akan membahas security dan best practice — menghindari persistensi token dan secret, memilih storage yang aman, sanitasi data dari API sebelum masuk store, serta best practice klasifikasi state, single responsibility, dan akses kontrol berbasis role.