Pinia Dasar — Vue

Pinia adalah state management library resmi Vue 3. // stores/counter.js export const useCounterStore = defineStore("counter", { state: () => ({ count: 0 })…

Pinia adalah library resmi Vue untuk menyimpan data yang dipakai banyak komponen di satu tempat bersama, di luar komponen mana pun. Tempat itu disebut store. Bayangin papan pesanan di dinding dapur Warung Bu Sri. Pelayan di depan menulis pesanan baru di papan, dan kasir di belakang membaca jumlahnya dari papan yang sama. Gak ada yang perlu mengoper kertas dari tangan ke tangan. Di aplikasi, ikon keranjang di header dan halaman menu sama-sama butuh isi keranjang; store adalah papan itu.

🧪 Tebakan cepat: IkonKeranjang menulis const { jumlah } = useKeranjangStore(), lalu menampilkan Keranjang ({{ jumlah }}). Di HalamanMenu, pembeli menekan "Tambah Nasi Goreng" dua kali. Ikonnya menampilkan angka berapa? Tebak dulu, jawabannya ada di bagian "storeToRefs: mengambil sebagian store".

Kenapa butuh store

Sejauh ini data dibagi lewat props (turun ke child), emit (naik ke parent), provide/inject, atau composable yang menaruh ref-nya di luar fungsi supaya semua komponen memegang ref yang sama. Pinia adalah cara resmi menulis "satu tempat bersama" itu, plus devtools untuk melihat isinya. Sebuah store punya tiga bagian:

Bagian store Mirip Isinya di keranjang
state ref / reactive jumlah, total
getters computed kosong, dihitung dari jumlah
actions fungsi tambah(harga), yang mengubah state

Memasang Pinia dan menulis store pertama

Pasang sekali di proyek (npm install pinia), lalu daftarkan di main.js:

import { createApp } from "vue"
import { createPinia } from "pinia"
import App from "./App.vue"

const app = createApp(App)
app.use(createPinia())
app.mount("#app")

Store ditulis di filenya sendiri dengan defineStore():

// stores/keranjang.js
import { defineStore } from "pinia"

export const useKeranjangStore = defineStore("keranjang", {
  state: () => ({ jumlah: 0, total: 0 }),
  getters: {
    kosong: (state) => state.jumlah === 0,
  },
  actions: {
    tambah(harga) {
      this.jumlah++
      this.total += harga
    },
  },
})

Memakai store di dua komponen

<!-- IkonKeranjang.vue -->
<script setup>
import { useKeranjangStore } from "../stores/keranjang"
const keranjang = useKeranjangStore()
</script>

<template>
  <span>Keranjang ({{ keranjang.jumlah }})</span>
</template>

<!-- HalamanMenu.vue -->
<script setup>
import { useKeranjangStore } from "../stores/keranjang"
const keranjang = useKeranjangStore()
</script>

<template>
  <button @click="keranjang.tambah(15000)">Tambah Nasi Goreng</button>
  <p v-if="keranjang.kosong">Keranjang masih kosong</p>
  <p v-else>{{ keranjang.jumlah }} item, Rp{{ keranjang.total }}</p>
</template>
Kejadian State store Ikon Halaman menu
Halaman tampil jumlah: 0, total: 0 Keranjang (0) Keranjang masih kosong
Tambah Nasi Goreng jumlah: 1, total: 15000 Keranjang (1) 1 item, Rp15000
Tambah Nasi Goreng lagi jumlah: 2, total: 30000 Keranjang (2) 2 item, Rp30000

Gaya setup: ref, computed, fungsi

Store yang sama bisa ditulis seperti <script setup>: berikan fungsi ke defineStore, lalu kembalikan semua yang mau dibagikan.

import { ref, computed } from "vue"
import { defineStore } from "pinia"

export const useKeranjangStore = defineStore("keranjang", () => {
  const jumlah = ref(0)
  const total = ref(0)
  const kosong = computed(() => jumlah.value === 0)

  function tambah(harga) {
    jumlah.value++
    total.value += harga
  }

  return { jumlah, total, kosong, tambah }
})

storeToRefs: mengambil sebagian store

Store adalah objek reactive. Kalau di-destructure, yang diambil cuma angkanya saat itu, bukan sambungannya.

const { jumlah } = useKeranjangStore()                // salah: angka biasa, beku di 0
const { jumlah } = storeToRefs(useKeranjangStore())   // benar: ref yang tetap tersambung
IkonKeranjang menulis Sesudah Tambah dua kali, ikon Halaman menu
const keranjang = useKeranjangStore() Keranjang (2) 2 item, Rp30000
const { jumlah } = useKeranjangStore() Keranjang (0) 2 item, Rp30000
const { jumlah } = storeToRefs(useKeranjangStore()) Keranjang (2) 2 item, Rp30000

Ikon dengan destructuring biasa tetap Keranjang (0). Itu jawaban tebakannya. Store-nya benar berisi 2, halaman menu juga menampilkan 2, tapi ikon memegang angka 0 yang disalin saat ia dibuat. storeToRefs (di-import dari "pinia") mengubah state dan getter jadi ref, jadi tetap tersambung. Action boleh di-destructure langsung tanpa storeToRefs: const { tambah } = keranjang tetap mengubah store yang sama.

📌 Store dipakai sebelum app.use(pinia)

Kalau useKeranjangStore() dipanggil sebelum Pinia didaftarkan (misalnya app.use(createPinia()) lupa ditulis), build development langsung berhenti dengan pesan ini:

[🍍]: "getActivePinia()" was called but there was no active Pinia. Are you trying to use a store before calling "app.use(pinia)"?

Build production gak menulis pesan itu dan cuma gagal dengan Cannot read properties of undefined (reading '_s'). Obatnya sama: app.use(createPinia()) sebelum app.mount(), dan panggil store di dalam komponen atau fungsi, bukan di baris paling atas sebuah file.

⚠️ Jebakan yang sering ketemu

TL;DR: Pinia menyimpan data bersama di store, di luar komponen. Pasang dengan app.use(createPinia()), tulis store dengan defineStore("id", { state, getters, actions }) atau gaya setup defineStore("id", () => { ...; return { ... } }), di mana ref jadi state, computed jadi getter, dan fungsi jadi action. Setiap komponen yang memanggil useKeranjangStore() mendapat objek yang sama, jadi perubahan dari satu komponen langsung terlihat di komponen lain. Jangan destructure store langsung: pakai storeToRefs() untuk state dan getter; action boleh diambil langsung.

Yang akan kamu pelajari