Manajemen state Vue 3 untuk panel admin: Pinia vs lokal
Manajemen state Vue 3 untuk panel admin: memilih antara Pinia, provide/inject, atau state lokal dengan contoh nyata seperti filter, draft, dan tab.

Apa yang membuat state jadi rumit di panel admin
Panel admin terasa penuh state karena banyak bagian bergerak dalam satu layar. Sebuah tabel bukan sekadar data — ia juga punya sorting, filter, pagination, baris terpilih, dan konteks “apa yang baru saja terjadi?” yang diandalkan pengguna. Tambahkan form panjang, permission berbasis peran, dan aksi yang mengubah apa yang boleh dilakukan UI, lalu keputusan state kecil jadi penting.
Tantangannya bukan menyimpan nilai. Tantangannya menjaga perilaku tetap dapat diprediksi saat beberapa komponen butuh kebenaran yang sama. Jika sebuah chip filter menunjukkan “Active”, tabel, URL, dan aksi ekspor harus sepakat. Jika pengguna mengedit sebuah record lalu berpindah, aplikasi tidak boleh diam-diam menghapus pekerjaan mereka. Jika mereka membuka dua tab, satu tab tidak boleh menimpa yang lain.
Di Vue 3, Anda biasanya memilih di antara tiga tempat menyimpan state:
- Local component state: dimiliki oleh satu komponen dan aman direset saat unmount.
provide/inject: state bersama yang berskala halaman atau area fitur, tanpa prop drilling.- Pinia: state bersama yang harus bertahan navigasi, digunakan ulang antar route, dan mudah didebug.
Cara berpikir yang berguna: untuk setiap potongan state, putuskan di mana harus tinggal agar tetap benar, tidak mengejutkan pengguna, dan tidak menjadi spaghetti.
Contoh di bawah fokus pada tiga masalah umum admin: filter dan tabel (apa yang harus dipertahankan vs direset), draft dan edit yang belum tersimpan (form yang bisa dipercaya), dan pengeditan multi-tab (menghindari tabrakan state).
Cara sederhana untuk mengklasifikasikan state sebelum memilih alat
Perdebatan soal state jadi lebih mudah saat Anda berhenti berdebat tentang alat dan pertama-tama menamai jenis state yang Anda punya. Jenis state berbeda berperilaku berbeda, dan mencampurnya yang menyebabkan bug aneh.
Pembagian praktis:
- UI state: toggle, dialog terbuka, baris terpilih, tab aktif, urutan sort.
- Server state: response API, flag loading, error, waktu terakhir refresh.
- Form state: nilai field, error validasi, flag dirty, draft yang belum tersimpan.
- Cross-screen state: apa pun yang beberapa route perlu baca atau ubah (workspace saat ini, permission bersama).
Kemudian tentukan scope. Tanyakan di mana state digunakan hari ini, bukan di mana mungkin digunakan nanti. Jika hanya penting di dalam satu komponen tabel, local state biasanya cukup. Jika dua komponen saudara di halaman yang sama membutuhkannya, masalah sebenarnya adalah sharing di tingkat halaman. Jika banyak route membutuhkannya, Anda berada di wilayah shared app state.
Selanjutnya adalah lifetime. Beberapa state harus direset saat menutup drawer. State lain harus bertahan navigasi (filter saat Anda klik ke record lalu kembali). Beberapa harus bertahan reload (draft panjang yang pengguna kembali nanti). Memperlakukan ketiganya sama adalah cara mendapatkan filter yang misterius ter-reset, atau draft yang menghilang.
Terakhir, periksa concurrency. Panel admin cepat menemui edge case: pengguna membuka record yang sama di dua tab, refresh latar belakang memperbarui baris sementara form kotor, atau dua editor berlomba menyimpan.
Contoh: layar “Users” dengan filter, tabel, dan drawer edit. Filter adalah UI state dengan lifetime halaman. Baris adalah server state. Field drawer adalah form state. Jika user yang sama diedit di dua tab, Anda butuh keputusan concurrency eksplisit: block, merge, atau warn.
Setelah Anda bisa menandai state berdasarkan tipe, scope, lifetime, dan concurrency, pilihan alat (local state, provide/inject, atau Pinia) biasanya jadi lebih jelas.
Cara memilih: proses keputusan yang tahan uji
Pilihan state yang baik dimulai dengan satu kebiasaan: jelaskan state dengan kata-kata sederhana sebelum memilih alat. Panel admin mencampur tabel, filter, form besar, dan navigasi antar record, jadi bahkan state “kecil” bisa jadi magnet bug.
Proses keputusan 5 langkah
-
Siapa yang butuh state?
- Satu komponen: simpan lokal.
- Beberapa komponen di bawah satu halaman: pertimbangkan
provide/inject. - Banyak route: pertimbangkan Pinia.
Filter adalah contoh bagus. Jika hanya mempengaruhi satu tabel yang memilikinya, local state cukup. Jika filter hidup di header dan mengendalikan tabel di bawahnya, sharing di level halaman (sering
provide/inject) menjaga kebersihan. -
Berapa lama harus bertahan?
- Jika boleh hilang saat komponen unmount, local state ideal.
- Jika harus bertahan perubahan route, Pinia sering pilihan lebih baik.
- Jika harus bertahan reload, Anda juga perlu persistence (storage), terlepas dari tempat penyimpanan.
Ini paling penting untuk draft. Edit yang belum tersimpan sensitif terhadap kepercayaan: orang mengharapkan draft tetap ada jika mereka pergi dan kembali.
-
Haruskah dibagi antar tab browser atau terisolasi per tab?
Pengeditan multi-tab tempat muncul bug. Jika tiap tab harus punya draft sendiri, hindari singleton global. Pilih state yang diberi kunci berdasarkan ID record, atau jaga agar page-scoped sehingga satu tab tidak menimpa yang lain.
-
Pilih opsi paling sederhana yang memenuhi kebutuhan.
Mulai dari lokal. Naikkan level hanya saat Anda merasakan sakit nyata: prop drilling, logika duplikat, atau reset yang sulit direproduksi.
-
Konfirmasi kebutuhan debugging Anda.
Jika Anda perlu view perubahan yang jelas dan dapat diinspeksi antar layar, aksi dan inspeksi terpusat Pinia bisa menghemat waktu. Jika state pendek umurnya dan jelas, local state lebih mudah dibaca.
Local component state: kapan cukup
Local state adalah default ketika data hanya penting untuk satu komponen di satu halaman. Mudah untuk melewatkan opsi ini dan overbuild store yang akan Anda rawat berbulan-bulan.
Kesesuaian jelas adalah satu tabel dengan filter sendiri. Jika filter hanya mempengaruhi satu tabel (mis. daftar Users) dan tidak ada yang lain bergantung, simpan sebagai ref di dalam komponen tabel. Hal yang sama berlaku untuk state UI kecil seperti “modal terbuka?”, “baris mana yang sedang diedit?”, dan “item mana yang terpilih sekarang?”.
Jangan menyimpan apa yang bisa dihitung. Badge “Active filters (3)” sebaiknya dihitung dari nilai filter saat ini. Label sort, ringkasan terformat, dan flag “bisa simpan” juga lebih baik sebagai computed karena otomatis tetap sinkron.
Aturan reset lebih penting daripada alat yang Anda pilih. Putuskan apa yang dibersihkan saat perubahan route (biasanya semuanya), dan apa yang tetap saat pengguna pindah-pindah di dalam halaman yang sama (mungkin pertahankan filter tapi bersihkan seleksi sementara untuk menghindari aksi bulk mengejutkan).
Local component state biasanya cukup ketika:
- State mempengaruhi satu widget (satu form, satu tabel, satu modal).
- Tidak ada layar lain yang perlu membacanya atau mengubahnya.
- Anda dapat mempertahankannya dalam 1–2 komponen tanpa meneruskan prop melalui banyak lapisan.
- Anda bisa menjelaskan perilaku reset dalam satu kalimat.
Batas utamanya adalah kedalaman. Saat Anda mulai meneruskan state yang sama lewat beberapa komponen bersarang, state lokal berubah menjadi prop drilling, dan itu biasanya tanda untuk pindah ke provide/inject atau store.
provide/inject: berbagi state di dalam halaman atau area fitur
provide/inject berada di antara local state dan store penuh. Parent “provide” nilai ke semua turunannya, dan komponen bersarang “inject” tanpa prop drilling. Di panel admin, ini cocok saat state milik satu layar atau area fitur, bukan seluruh aplikasi.
Polanya umum adalah page shell yang memiliki state sementara komponen kecil mengonsumsinya: filter bar, tabel, toolbar aksi massal, drawer detail, dan banner “perubahan belum disimpan”. Shell bisa provide permukaan reaktif kecil seperti objek filters, objek draftStatus (dirty, saving, error), dan beberapa flag baca-saja (mis. isReadOnly berdasarkan permission).
Apa yang harus di-provide (jaga agar kecil)
Jika Anda provide segala sesuatu, Anda pada dasarnya membuat ulang store dengan struktur lebih longgar. Provide hanya apa yang benar-benar beberapa child butuhkan. Filter adalah contoh klasik: ketika tabel, chip, aksi ekspor, dan pagination harus tetap sinkron, lebih baik berbagi satu sumber kebenaran daripada juggling props dan event.
Kejelasan dan jebakan
Risiko terbesar adalah dependensi tersembunyi: child “hanya bekerja” karena sesuatu di atasnya menyediakan data, lalu sulit menelusuri dari mana update berasal. Untuk menjaga keterbacaan dan testabilitas, beri nama injection dengan jelas (sering menggunakan konstanta atau Symbol). Juga lebih baik menyediakan aksi daripada hanya objek yang bisa dimutasi. API kecil seperti setFilter, markDirty, dan resetDraft membuat kepemilikan dan perubahan yang diperbolehkan menjadi jelas.
Pinia: state bersama dan update yang dapat diprediksi antar layar
Pinia unggul ketika state yang sama harus konsisten antar route dan komponen. Di panel admin, itu sering berarti user saat ini, apa yang mereka boleh lakukan, workspace/organisasi yang dipilih, dan setting aplikasi. Akan melelahkan jika setiap layar mengimplementasikannya ulang.
Store membantu karena memberi satu tempat untuk membaca dan memperbarui state bersama. Alih-alih meneruskan props melalui banyak lapisan, Anda import store di tempat perlu. Saat pindah dari list ke detail, bagian UI lain masih bisa bereaksi pada org terpilih, permission, dan setting yang sama.
Kenapa Pinia terasa lebih mudah dirawat
Pinia mendorong struktur sederhana: state untuk nilai mentah, getters untuk nilai turunan, dan actions untuk update. Di UI admin, struktur itu mencegah “perbaikan cepat” berubah jadi mutasi tersebar.
Jika canEditUsers tergantung pada role saat ini plus feature flag, taruh aturan itu di getter. Jika mengganti org perlu membersihkan seleksi cache dan reload navigasi, taruh urutan itu di action. Anda akan punya lebih sedikit watcher misterius dan lebih sedikit momen “kenapa ini berubah?”.
Pinia juga bekerja baik dengan Vue DevTools. Saat bug muncul, lebih mudah memeriksa state store dan melihat aksi mana yang dijalankan daripada mengejar perubahan di objek reaktif acak yang dibuat di komponen.
Hindari store sebagai tempat menumpuk semua hal
Store global terasa rapi pada awalnya, lalu jadi laci barang bekas. Kandidat bagus untuk Pinia adalah concern yang benar-benar dibagi seperti identitas user dan permissions, workspace terpilih, feature flags, dan data referensi bersama yang dipakai di banyak layar.
Kekhawatiran khusus halaman (mis. input sementara satu form) sebaiknya tetap lokal kecuali beberapa route benar-benar membutuhkannya.
Contoh 1: filter dan tabel tanpa menjadikan semuanya store
Bayangkan halaman Orders: tabel, filter (status, rentang tanggal, customer), pagination, dan panel samping yang mempratinjau order terpilih. Ini cepat menjadi berantakan karena mudah tergoda memasukkan setiap filter dan pengaturan tabel ke store global.
Cara sederhana memilih adalah memutuskan apa yang harus diingat, dan di mana:
- Hanya ingatan sementara (local atau provide/inject): direset saat Anda meninggalkan halaman. Bagus untuk state disposable.
- Query params: bisa dibagikan dan bertahan reload. Cocok untuk filter dan pagination yang ingin disalin orang.
- Pinia: bertahan navigasi. Bagus untuk “kembali ke daftar persis seperti saya tinggalkan.”
Dari situ, implementasi biasanya mengikuti:
Jika tidak ada yang mengharapkan pengaturan bertahan navigasi, simpan filters, sort, page, dan pageSize di dalam komponen Orders page, dan biarkan page memicu fetch. Jika toolbar, tabel, dan preview panel semuanya butuh model yang sama dan prop drilling jadi berisik, pindahkan model list ke page shell dan bagikan lewat provide/inject. Jika ingin daftar terasa lengket antar route (buka order, pergi ke tempat lain, kembali dengan filter dan seleksi sama), Pinia adalah pilihan yang lebih baik.
Aturan praktis: mulai lokal, pindah ke provide/inject ketika beberapa child butuh model yang sama, dan gunakan Pinia hanya saat benar-benar perlu persistence lintas-route.
Contoh 2: draft dan edit yang belum tersimpan (membangun kepercayaan pengguna)
Bayangkan agen support mengedit record customer: detail kontak, info tagihan, dan catatan internal. Mereka terganggu, pindah layar, lalu kembali. Jika form melupakan kerja mereka atau menyimpan data setengah jadi, kepercayaan hilang.
Untuk draft, pisahkan tiga hal: record terakhir yang tersimpan, editan staged pengguna, dan state UI seperti error validasi.
Local state: editan staged dengan aturan dirty yang jelas
Jika layar edit bersifat self-contained, local component state seringkali paling aman. Simpan salinan draft dari record, lacak isDirty (atau peta dirty per field), dan simpan error dekat kontrol form.
Alur sederhana: load record, clone menjadi draft, edit draft, dan hanya kirim request save saat pengguna klik Save. Cancel membuang draft dan reload.
provide/inject: satu draft dibagi di beberapa bagian bersarang
Form admin sering terbagi menjadi tab atau panel (Profile, Addresses, Permissions). Dengan provide/inject, Anda bisa menjaga satu model draft dan expose API kecil seperti updateField(), resetDraft(), dan validateSection(). Setiap bagian membaca dan menulis draft yang sama tanpa meneruskan props lewat banyak lapisan.
Kapan Pinia membantu untuk draft
Pinia berguna ketika draft harus bertahan navigasi atau terlihat di luar halaman edit. Pola umum adalah draftsById[customerId], sehingga setiap record punya draft sendiri. Ini juga membantu saat pengguna bisa membuka beberapa layar edit.
Bug draft biasanya muncul dari beberapa kesalahan yang bisa diprediksi: membuat draft sebelum record dimuat, menimpa draft kotor saat refetch, lupa membersihkan error saat cancel, atau memakai satu shared key yang membuat draft saling menimpa. Jika Anda tetapkan aturan jelas (kapan buat, timpa, buang, persist, dan ganti setelah save), sebagian besar masalah hilang.
Jika Anda membangun layar admin dengan AppMaster, pemisahan “draft vs saved record” tetap berlaku: simpan draft di client, dan anggap backend sebagai sumber kebenaran hanya setelah Save sukses.
Contoh 3: pengeditan multi-tab tanpa tabrakan state
Multi-tab editing adalah tempat panel admin sering rusak. Pengguna membuka Customer A, lalu Customer B, berpindah-pindah, dan mengharapkan tiap tab mengingat perubahan yang belum disimpan.
Solusinya adalah memodelkan tiap tab sebagai bundel state sendiri, bukan satu draft bersama. Setiap tab butuh setidaknya kunci unik (sering berdasarkan ID record), data draft, status (clean, dirty, saving), dan error field.
Jika tab hidup di dalam satu layar, pendekatan lokal bekerja baik. Simpan daftar tab dan draft dimiliki oleh komponen halaman yang merender tab. Setiap panel editor membaca dan menulis hanya bundel miliknya. Saat tab ditutup, hapus bundel itu dan selesai. Ini menjaga isolasi dan mudah dipahami.
Bentuk state serupa di mana pun ia berada:
- Daftar objek tab (masing-masing dengan
customerId,draft,status, danerrors) activeTabKey- Aksi seperti
openTab(id),updateDraft(key, patch),saveTab(key), dancloseTab(key)
Pinia menjadi pilihan bila tab harus bertahan navigasi (lompat ke Orders lalu kembali) atau saat banyak layar perlu membuka dan fokus tab. Dalam kasus itu, store kecil “tab manager” menjaga perilaku konsisten di seluruh aplikasi.
Tabrakan utama yang harus dihindari adalah variabel global tunggal seperti currentDraft. Ia bekerja sampai tab kedua dibuka, lalu edit saling timpa, error validasi muncul di tempat yang salah, dan Save memperbarui record yang keliru. Saat setiap tab punya bundel sendiri, tabrakan hilang hampir secara desain.
Kesalahan umum yang menyebabkan bug dan kode berantakan
Kebanyakan bug panel admin bukan “bug Vue.” Mereka bug state: data tinggal di tempat yang salah, dua bagian layar tidak sepakat, atau state lama diam-diam tetap ada.
Berikut pola yang sering muncul:
Memasukkan segala sesuatu ke Pinia secara default membuat kepemilikan tidak jelas. Store global terasa rapi awalnya, tapi kemudian setiap halaman baca dan tulis objek yang sama, dan pembersihan jadi tebakan.
Menggunakan provide/inject tanpa kontrak yang jelas menciptakan dependensi tersembunyi. Jika child inject filters tapi tak ada pemahaman bersama siapa yang menyediakan dan aksi apa yang boleh mengubahnya, Anda akan mendapat update kejutan ketika child lain mulai memutasi objek yang sama.
Mencampur server state dan UI state dalam store yang sama menyebabkan overwrite tidak sengaja. Record yang difetch berperilaku berbeda dari “apakah drawer terbuka?”, “tab saat ini”, atau “field dirty.” Saat dicampur, refetch bisa menimpa UI, atau perubahan UI bisa merusak cache data.
Melewatkan cleanup lifecycle membuat state bocor. Filter dari satu view bisa mempengaruhi view lain, dan draft bisa tetap setelah meninggalkan halaman. Lain kali seseorang membuka record berbeda, mereka melihat seleksi lama dan mengira aplikasi rusak.
Memberi kunci draft dengan buruk adalah pembunuh kepercayaan yang sunyi. Jika Anda simpan draft di bawah satu kunci seperti draft:editUser, mengedit User A lalu User B menimpa draft yang sama.
Aturan sederhana mencegah sebagian besar ini: jaga state sedekat mungkin dengan tempat digunakannya, dan angkat hanya saat dua bagian independen benar-benar perlu berbagi. Saat Anda berbagi, definisikan kepemilikan (siapa yang boleh mengubah) dan identitas (bagaimana ia dikunci).
Daftar cek cepat sebelum memilih local, provide/inject, atau Pinia
Pertanyaan paling berguna adalah: siapa pemilik state ini? Jika Anda tidak bisa menyatakannya dalam satu kalimat, state itu kemungkinan melakukan terlalu banyak dan sebaiknya dipisah.
Gunakan pengecekan ini sebagai filter cepat:
- Bisakah Anda menamai pemilik (sebuah komponen, sebuah halaman, atau seluruh app)?
- Haruskah ia bertahan perubahan route atau reload? Jika ya, rencanakan persistence alih-alih berharap browser menyimpannya.
- Apakah dua record pernah diedit bersamaan? Jika ya, kunci state berdasarkan ID record.
- Apakah state hanya dipakai oleh komponen di bawah satu page shell? Jika ya,
provide/injectsering cocok. - Perlukah Anda menginspeksi perubahan dan memahami siapa mengubah apa? Jika ya, Pinia sering tempat yang paling bersih untuk irisan itu.
Pencocokan alat, secara sederhana:
Jika state lahir dan mati di dalam satu komponen (mis. flag dropdown terbuka/tertutup), simpan lokal. Jika beberapa komponen di layar sama butuh konteks bersama (filter bar + tabel + ringkasan), provide/inject menjaga agar tidak global. Jika state harus dibagi antar layar, bertahan navigasi, atau butuh update yang dapat diprediksi dan didebug, gunakan Pinia dan beri kunci entri berdasarkan ID record saat draft terlibat.
Jika Anda membangun UI admin Vue 3 (termasuk yang digenerate dengan alat seperti AppMaster), daftar cek ini membantu menghindari memasukkan segala sesuatu ke store terlalu dini.
Langkah selanjutnya: mengembangkan state tanpa membuat kekacauan
Cara paling aman untuk memperbaiki manajemen state di panel admin adalah mengembangkannya langkah demi langkah. Mulai dengan state lokal untuk apa pun yang tetap di dalam satu halaman. Saat melihat reuse nyata (logika yang disalin, komponen ketiga butuh state yang sama), angkat ke level lebih tinggi. Baru pertimbangkan shared store.
Jalur yang bekerja untuk kebanyakan tim:
- Simpan state khusus halaman lokal dulu (filter, sort, pagination, panel terbuka/tertutup).
- Gunakan
provide/injectketika beberapa komponen di halaman yang sama butuh konteks bersama. - Tambahkan satu store Pinia pada satu waktu untuk kebutuhan lintas-layar (draft manager, tab manager, current workspace).
- Tulis aturan reset dan patuhi (apa yang reset saat navigasi, logout, Clear filters, Discard changes).
Aturan reset kecil tapi mencegah sebagian besar momen “kenapa ini berubah?”. Tentukan, misalnya, apa yang terjadi pada draft ketika seseorang membuka record lain lalu kembali: pulihkan, beri peringatan, atau reset. Lalu buat perilaku itu konsisten.
Jika Anda memperkenalkan store, bentukkan berdasarkan fitur. Store drafts seharusnya menangani membuat, mengembalikan, dan membersihkan draft, tapi tidak seharusnya juga memegang filter tabel atau flag layout UI.
Jika ingin prototipe panel admin cepat, AppMaster (appmaster.io) bisa menghasilkan aplikasi web Vue3 plus backend dan logika bisnis, dan Anda tetap bisa menyempurnakan kode yang dihasilkan di tempat Anda butuh penanganan state kustom. Langkah praktis berikutnya adalah membangun satu layar ujung-ke-ujung (mis. form edit dengan pemulihan draft) dan lihat apa yang benar-benar perlu Pinia versus apa yang bisa tetap lokal.
FAQ
Gunakan state lokal ketika data hanya berdampak pada satu komponen dan boleh hilang saat komponen tersebut unmount. Contoh umum: dialog buka/tutup, baris terpilih di satu tabel, atau bagian form yang tidak digunakan ulang di tempat lain.
Pakai provide/inject ketika beberapa komponen di halaman yang sama butuh satu sumber kebenaran bersama dan prop drilling mulai merepotkan. Batasi apa yang Anda provide agar halaman tetap mudah dipahami.
Gunakan Pinia saat state harus dibagi antar route, bertahan saat navigasi, atau perlu mudah diinspeksi dan didebug di satu tempat. Contoh umum: current workspace, permissions, feature flags, dan manajer lintas-layar seperti drafts atau tabs.
Mulai dengan memberi nama tipe state (UI, server, form, cross-screen), lalu tentukan scope (satu komponen, satu halaman, banyak route), lifetime (hilang saat unmount, bertahan navigasi, bertahan reload), dan concurrency (single editor atau multi-tab). Pilihan tool biasanya mengikuti dari keempat label itu.
Jika pengguna perlu membagikan atau mengembalikan tampilan, letakkan filter dan pagination di query params agar bertahan reload dan bisa disalin. Jika tujuannya hanya “kembali ke daftar seperti saat saya tinggalkan” antar route, simpan model di Pinia; jika tidak, cukup page-scoped.
Pisahkan record terakhir yang tersimpan, editan yang sedang disusun pengguna (draft), dan state UI seperti error validasi. Hanya tulis kembali ke backend saat pengguna menekan Save. Tentukan aturan dirty dan apa yang terjadi saat navigasi (peringatan, auto-save, atau pemulihan) agar pengguna tidak kehilangan pekerjaan.
Berikan setiap editor terbuka bundel state sendiri yang diberi kunci berdasarkan ID record (dan kadang kunci tab), jangan gunakan satu currentDraft global. Ini mencegah satu tab menimpa edit atau error tab lain.
Jika alur edit terisolasi dalam satu route, provide/inject yang dimiliki halaman bisa cukup. Jika draft harus bertahan antar navigasi atau diakses di luar layar edit, Pinia dengan pola seperti draftsById[recordId] biasanya lebih sederhana dan dapat diprediksi.
Jangan simpan apa yang bisa dihitung. Derivasi seperti badge jumlah filter, ringkasan, dan flag “bisa simpan” sebaiknya dibuat dari state saat ini dengan computed agar tidak pernah tidak sinkron.
Memindahkan semua ke Pinia terlalu dini, mencampur server state dengan toggle UI, dan gagal membersihkan saat navigasi adalah sumber masalah paling umum. Juga berhati-hati pada kunci yang buruk seperti satu shared draft key yang dipakai untuk berbagai record.


