## 👉 [Demo on CodeSandbox](https://j4qzw.csb.app/)
-Pinia works both for Vue 2.x and Vue 3.x and you are currently on the branch that supports Vue 3.x. [Go here for the Vue 2.x compatible version](https://github.com/posva/pinia/tree/v1).
-
## Help me keep working on this project 💚
- [Become a Sponsor on GitHub](https://github.com/sponsors/posva)
],
sidebar: {
- // '/config/': 'auto',
- // '/plugins/': 'auto',
// catch-all fallback
'/': [
{
- text: 'Guide',
+ text: 'Introduction',
children: [
{
- text: 'Installation',
- link: '/guide/installation.html',
+ text: 'What is Pinia?',
+ link: '/introduction.html',
},
{
text: 'Getting Started',
- link: '/guide/',
+ link: '/getting-started.html',
},
- // {
- // text: 'Features',
- // link: '/guide/features',
- // },
+ ],
+ },
+ {
+ text: 'Core Concepts',
+ children: [
+ { text: 'Defining a Store', link: '/core-concepts/' },
+ { text: 'State', link: '/core-concepts/state.html' },
+ { text: 'Getters', link: '/core-concepts/getters.html' },
+ { text: 'Actions', link: '/core-concepts/actions.html' },
+ ],
+ },
+ {
+ text: 'Server-Side Rendering (SSR)',
+ link: '/ssr.html',
+ },
+ {
+ text: 'Cookbook',
+ link: '/cookbook/',
+ children: [
{
- text: 'Server-Side Rendering (SSR)',
- link: '/guide/ssr.html',
+ text: 'Composing Stores',
+ link: '/cookbook/composing-stores.html',
},
],
},
.custom-block.tip {
border-color: var(--c-brand-light);
+ background-color: var(--c-bg-accent);
}
.carbon-ads,
--- /dev/null
+# Composing Stores
+
+Composing stores may look hard at first glance but there is only one rule to follow really:
+
+If **multiple stores use each other** or you need to use **multiple stores** at the same time, you must create a **separate file** where you import all of them.
+
+If one store uses an other store, there is no need to create a new file, you can directly import it. Think of it as nesting.
+
+You can call `useOtherStore()` at the top of any getter an action:
+
+```ts
+import { useUserStore } from './user'
+
+export const cartStore = defineStore({
+ id: 'cart',
+ getters: {
+ // ... other getters
+ summary() {
+ const user = useUserStore()
+
+ return `Hi ${user.name}, you have ${this.list.length} items in your cart. It costs ${this.price}.`
+ },
+ },
+
+ actions: {
+ purchase() {
+ const user = useUserStore()
+
+ return apiPurchase(user.id, this.list)
+ },
+ },
+})
+```
+
+## Shared Getters
+
+If you need to compute a value based on the `state` and/or `getters` of multiple stores, you may be able to import all the stores but one into the remaining store, but depending on how your stores are used across your application, **this would hurt your code splitting** because importing the store that imports all others stores, would result in **one single big chunk** with all of your stores.
+To prevent this, **we follow the rule above** and we create a new file with a new store:
+
+```ts
+import { defineStore } from 'pinia'
+import { useUserStore } from './user'
+import { useCartStore } from './cart'
+
+export const useSharedStore = defineStore({
+ id: 'shared',
+ getters: {
+ summary() {
+ const user = useUserStore()
+ const cart = useCartStore()
+
+ return `Hi ${user.name}, you have ${cart.list.length} items in your cart. It costs ${cart.price}.`
+ },
+ },
+})
+```
+
+## Shared Actions
+
+When an actions needs to use multiple stores, we do the same, we create a new file with a new store:
+
+```ts
+import { defineStore } from 'pinia'
+import { useUserStore } from './user'
+import { useCartStore } from './cart'
+
+export const useSharedStore = defineStore({
+ id: 'shared',
+ state: () => ({}),
+ actions: {
+ async orderCart() {
+ const user = useUserStore()
+ const cart = useCartStore()
+
+ try {
+ await apiOrderCart(user.token, cart.items)
+ cart.emptyCart()
+ } catch (err) {
+ displayError(err)
+ }
+ },
+ },
+})
+```
--- /dev/null
+# Cookbook
+
+- [Composing Stores](./composing-stores.md): How to cross use multiple stores. e.g. using the user store in the cart store.
--- /dev/null
+# Actions
+
+Actions are the equivalent of [methods](https://v3.vuejs.org/guide/data-methods.html#methods) in components. They can be defined with the `actions` property in `defineStore()` and it's a perfect place to define business logic:
+
+```js
+export const useStore = defineStore({
+ id: 'main',
+ state: () => ({
+ counter: 0,
+ }),
+ actions: {
+ reset() {
+ this.counter = 0
+ },
+ },
+})
+```
+
+Like [getters](./getters.md), actions get access to the _whole store instance_ through `this` with full typing (and autocompletion ✨) support.
+
+Actions are invoked like methods:
+
+```ts
+export default defineComponent({
+ setup() {
+ const main = useMainStore()
+ // call the action as a method of the store
+ main.reset()
+
+ return {}
+ },
+})
+```
--- /dev/null
+# Getters
+
+Getters are exactly the equivalent of [computed values](https://v3.vuejs.org/guide/reactivity-computed-watchers.html#computed-values) for the state of a Store. They can be defined with the `getters` property in `defineStore()`:
+
+```js
+export const useStore = defineStore({
+ id: 'main',
+ state: () => ({
+ counter: 0,
+ }),
+ getters: {
+ doubleCount() {
+ return this.counter * 2
+ },
+ },
+})
+```
+
+Like [actions](./actions.md), getters get access to the _whole store instance_ through `this` with full typing (and autocompletion ✨) support.
+
+Then you can access the getter directly on the store instance:
+
+```vue
+<template>
+ <p>Double count is {{ store.doubleCount }}</p>
+</template>
+
+<script>
+export default {
+ setup() {
+ const store = useStore()
+
+ return { store }
+ },
+}
+</script>
+```
+
+## Accessing other getters
+
+As with computed properties, you can combine multiple getters. Access any other getter via `this`:
+
+```js
+export const useStore = defineStore({
+ id: 'main',
+ state: () => ({
+ counter: 0,
+ }),
+ getters: {
+ doubleCount() {
+ return this.counter * 2
+ },
+ doubleCountPlusOne() {
+ // autocompletion ✨
+ return this.doubleCount * 2
+ },
+ },
+})
+```
+
+## Accessing other stores
+
+```js
+export const useStore = defineStore({
+ id: 'main',
+ state: () => ({
+ // ...
+ }),
+ getters: {
+ otherGetter() {
+ const otherStore = useOtherStore()
+ return this.localData + otherStore.data
+ },
+ },
+})
+```
--- /dev/null
+# Defining a Store
+
+Before diving into core concepts, we need to know that a store is defined using `defineStore()` and that it requires an `id` property, **unique** across all of your stores:
+
+```js
+import { defineStore } from 'pinia'
+
+// useStore could be anything like useUser, useCart
+export useStore = defineStore({
+ // unique id of the store across your application
+ id: 'storeName'
+})
+```
+
+The `id` is necessary and is used by `pinia` to connect the store to the devtools.
+
+## Using the store
+
+We are _defining_ a store because the store won't be created until `useStore()` is called inside of `setup()`. You can define as many stores as you want and you should define each store in a different file to automatically allow your bundle to code split.
+
+Avoid calling `useStore()` outside of a `setup()` function:
+
+```js
+import { createRouter } from 'vue-router'
+const router = createRouter({
+ // ...
+})
+
+// ❌ Depending on where you do this it will fail
+const store = useStore()
+
+router.beforeEach((to, from, next) => {
+ if (store.isLoggedIn) next()
+ else next('/login')
+})
+```
+
+Instead, call `useStore()` inside functions that are called after `app.use(pinia)`:
+
+```js
+router.beforeEach((to) => {
+ // ✅ This will work because the router starts its navigation after pinia is installed
+ const store = useStore()
+
+ if (to.meta.requiresAuth && !store.isLoggedIn) return '/login'
+})
+```
+
+:::tip
+When dealing with Server Side Rendering, you will have to pass the `pinia` instance to `useStore()`. Read more about this in the [SSR guide](./server-side-rendering.md).
+:::
+
+Once the store is instantiated, you can access any property defined in `state` and `getters` directly on the store, similar to `data` and `computed` properties in a vue component.
+
+```js
+// inside a component
+export default defineComponent({
+ setup() {
+ const store = useStore()
+ const text = store.name // "eduardo"
+ store.doubleCount // 2
+
+ return {
+ text, // will always be "eduardo"
+ doubleValue: computed(() => store.doubleValue), // reactive value
+ }
+ },
+})
+```
+
+`store` in an object wrapped with `reactive`, meaning there is no need to write `.value` after getters but, like `props` in `setup`, we cannot destructure it:
+
+```ts
+export default defineComponent({
+ setup() {
+ // ❌ This won't work because it breaks reactivity
+ // it's the same as destructuring from `props`
+ const { name, doubleCount } = useStore()
+ return { name, doubleCount }
+ },
+})
+```
--- /dev/null
+# State
+
+The state is, most of the time, the central part of your store. People often start by defining the state that represents their app. In Pinia the state is defined as a function that returns the initial state. \_This allows Pinia to work in both Server and Client Side.
+
+```js
+import { defineStore } from 'pinia'
+
+const useStore = defineStore({
+ id: 'storeName',
+ state() {
+ return {
+ // all these properties will have their type inferred automatically
+ counter: 0,
+ name: 'Eduardo',
+ isAdmin: true,
+ }
+ },
+})
+```
+
+## Accessing the `state`
+
+You can directly read and write to the state by accessing it through the `store` instance:
+
+```js
+store.counter++
+```
+
+or call the method `$patch` that allows you apply multiple changes at the same time with a partial `state` object:
+
+```ts
+store.$patch({
+ counter: -1,
+ name: 'Abalam',
+})
+```
+
+The main difference here is that `$patch` allows you to group multiple changes into one single entry in the devtools.
+
+## Replacing the `state`
+
+Simply set your store `$stet` property to a new object:
+
+```ts
+store.$state = { counter: 666, name: 'Paimon' }
+```
--- /dev/null
+## Installation
+
+Install `pinia` with your favorite package manager:
+
+```bash
+yarn add pinia@next
+# or with npm
+npm install pinia@next
+```
+
+:::tip
+Install Pinia v2 for Vue 3 `pinia@next` and Pinia v1 `pinia@latest` for Vue 2.
+:::
+
+Create a pinia (the root store) and pass it to app:
+
+```js
+import { createPinia } from 'pinia'
+
+app.use(createPinia())
+```
+
+If you are using Vue 2, do:
+
+```js
+import { createPinia, PiniaPlugin } from 'pinia'
+
+Vue.use(PiniaPlugin)
+const pinia = createPinia()
+
+new Vue({
+ el: '#app',
+ // other options...
+ pinia,
+})
+```
+
+This will also add devtools support. In Vue 3, some features like time traveling and editing are still not supported because vue-devtools doesn't expose the necessary APIs yet. In Vue 2, Pinia uses the existing interface for Vuex (and can therefore not be used alongside it).
+
+## What is a Store?
+
+A Store (like Pinia) is an entity holding state and business logic that isn't bound to your Component tree. In other words, **it hosts global state**. It's a bit like a component that is always there and that everybody can read off and write to. It has **three concepts**, the [state](./state.md), [getters](./getters.md) and [actions](./actions.md).
+
+## When should I use a Store
+
+A store should contain data that can be accessed throughout your application. This includes data that is used in many places, e.g. User information that is displayed in the navbar, as well as data that needs to be preserved through pages, e.g. a very complicated multi-step form.
+
+On the other hand, you should avoid including in the store local data that could be hosted in a component instead, e.g. the visibility of an element local to a page.
+
+Not all applications need access to a global state, but if yours need one, Pinia will make your life easier.
+++ /dev/null
-## Install the plugin
-
-Create a pinia (the root store) and pass it to app:
-
-```js
-import { createPinia } from 'pinia'
-
-app.use(createPinia())
-```
-
-This will also add devtools support. Some features like time traveling and editing are still not supported because vue-devtools doesn't expose the necessary APIs yet.
-
-## Creating a Store
-
-You can create as many stores as you want, and they should each exist in different files:
-
-```ts
-import { defineStore } from 'pinia'
-
-export const useMainStore = defineStore({
- // name of the store
- // it is used in devtools and allows restoring state
- id: 'main',
- // a function that returns a fresh state
- state: () => ({
- counter: 0,
- name: 'Eduardo',
- }),
- // optional getters
- getters: {
- doubleCount() {
- return this.counter * 2
- },
- // use getters in other getters
- doubleCountPlusOne() {
- return this.doubleCount * 2
- },
- },
- // optional actions
- actions: {
- reset() {
- // `this` is the store instance
- this.counter = 0
- },
- },
-})
-```
-
-`defineStore` returns a function that has to be called to get access to the store:
-
-```ts
-import { useMainStore } from '@/stores/main'
-
-export default defineComponent({
- setup() {
- const main = useMainStore()
-
- return {
- // gives access to the whole store
- main,
- // gives access only to specific state
- state: computed(() => main.counter),
- // gives access to specific getter; like `computed` properties
- doubleCount: computed(() => main.doubleCount),
- }
- },
-})
-```
-
-Note: the SSR implementation on Pinia might change, but if you intend having SSR on your application, you should avoid using `useStore` functions at the root level of a file to make sure the correct store is retrieved for your currently running application instance. Here is an example:
-
-**Avoid doing this**:
-
-```ts
-import { createRouter } from 'vue-router'
-const router = createRouter({
- // ...
-})
-
-// ❌ Depending on where you do this it will fail
-const main = useMainStore()
-
-router.beforeEach((to, from, next) => {
- if (main.isLoggedIn) next()
- else next('/login')
-})
-```
-
-Instead, call `useMainStore()` at the top of `setup`, like `inject` and `provide` in Vue:
-
-```ts
-export default defineComponent({
- setup() {
- // ✅ This will work
- const main = useMainStore()
-
- return {}
- },
-})
-
-// In a different file...
-const pinia = createPinia()
-app.use(pinia)
-
-router.beforeEach((to) => {
- // ✅ This will work (requires pinia param when outside of setup on both
- // Client and Server. See the SSR section below for more information)
- const main = useMainStore(pinia)
-
- if (to.meta.requiresAuth && !main.isLoggedIn) return '/login'
-})
-```
-
-⚠️: Note that if you are developing an SSR application, [you will need to do a bit more](#ssr).
-
-You can access any property defined in `state` and `getters` directly on the store, similar to `data` and `computed` properties in a Vue component.
-
-```ts
-export default defineComponent({
- setup() {
- const main = useMainStore()
- const text = main.name // "eduardo"
- const doubleCount = main.doubleCount // 2
-
- return {
- text, // will always be "eduardo"
- textDynamic: computed(() => main.name), // reactive value
- }
- },
-})
-```
-
-The `main` store in an object wrapped with `reactive`, meaning there is no need to write `.value` after getters but, like `props` in `setup`, we cannot destructure it:
-
-```ts
-export default defineComponent({
- setup() {
- // ❌ This won't work because it breaks reactivity
- // it's the same as destructuring from `props`
- const { name, doubleCount } = useMainStore()
- return { name, doubleCount }
- },
-})
-```
-
-Actions are invoked like methods:
-
-```ts
-export default defineComponent({
- setup() {
- const main = useMainStore()
- // call the action as a method of the store
- main.reset()
-
- return {}
- },
-})
-```
-
-## Mutating the `state`
-
-To mutate the state you can either directly change something:
-
-```ts
-main.counter++
-```
-
-or call the method `$patch` that allows you apply multiple changes at the same time with a partial `state` object:
-
-```ts
-main.$patch({
- counter: -1,
- name: 'Abalam',
-})
-```
-
-The main difference here is that `$patch` allows you to group multiple changes into one single entry in the devtools.
-
-## Replacing the `state`
-
-Simply set your store `$stet` property to a new object:
-
-```ts
-main.$state = { counter: 666, name: 'Paimon' }
-```
-
-## Composing Stores
-
-Composing stores may look hard at first glance but there is only one rule to follow really:
-
-If **multiple stores use each other** or you need to use **multiple stores** at the same time, you must create a **separate file** where you import all of them.
-
-If one store uses an other store, there is no need to create a new file, you can directly import it. Think of it as nesting.
-
-You can call `useOtherStore()` at the top of any getter an action:
-
-```ts
-import { useUserStore } from './user'
-
-export const cartStore = defineStore({
- id: 'cart',
- getters: {
- // ... other getters
- summary() {
- const user = useUserStore()
-
- return `Hi ${user.name}, you have ${this.list.length} items in your cart. It costs ${this.price}.`
- },
- },
-
- actions: {
- purchase() {
- const user = useUserStore()
-
- return apiPurchase(user.id, this.list)
- },
- },
-})
-```
-
-### Shared Getters
-
-If you need to compute a value based on the `state` and/or `getters` of multiple stores, you may be able to import all the stores but one into the remaining store, but depending on how your stores are used across your application, **this would hurt your code splitting** because importing the store that imports all others stores, would result in **one single big chunk** with all of your stores.
-To prevent this, **we follow the rule above** and we create a new file with a new store:
-
-```ts
-import { defineStore } from 'pinia'
-import { useUserStore } from './user'
-import { useCartStore } from './cart'
-
-export const useSharedStore = defineStore({
- id: 'shared',
- getters: {
- summary() {
- const user = useUserStore()
- const cart = useCartStore()
-
- return `Hi ${user.name}, you have ${cart.list.length} items in your cart. It costs ${cart.price}.`
- },
- },
-})
-```
-
-### Shared Actions
-
-When an actions needs to use multiple stores, we do the same, we create a new file with a new store:
-
-```ts
-import { defineStore } from 'pinia'
-import { useUserStore } from './user'
-import { useCartStore } from './cart'
-
-export const useSharedStore = defineStore({
- id: 'shared',
- state: () => ({}),
- actions: {
- async orderCart() {
- const user = useUserStore()
- const cart = useCartStore()
-
- try {
- await apiOrderCart(user.token, cart.items)
- cart.emptyCart()
- } catch (err) {
- displayError(err)
- }
- },
- },
-})
-```
+++ /dev/null
-# Installation
-
-```bash
-yarn add pinia@next
-# or with npm
-npm install pinia@next
-```
--- /dev/null
+# Introduction
+
+Pinia [started](https://github.com/posva/pinia/commit/06aeef54e2cad66696063c62829dac74e15fd19e) as an experiment to redesign what a Store for Vue could look like with the [Composition API](https://github.com/vuejs/composition-api) around November 2019. Since then, the initial principals are still the same but Pinia works for both Vue 2 and Vue 3. The API is the same for both except for _installation_ and _SSR_ and these docs are targeted to Vue 3 with notes about Vue 2 whenever necessary so it can be used no matter if you are using Vue 2 or Vue 3!
+
+## Basic example
+
+You start by creating a store:
+
+```js
+import { defineStore } from 'pinia'
+
+export const useCounter = defineStore({
+ id: 'counter',
+ state: () => ({ count: 0 }),
+})
+```
+
+And then you _use_ it in a component:
+
+```js
+export default {
+ setup() {
+ const counter = useCounter()
+
+ counter.count++
+ // with autocompletion ✨
+ counter.$patch({ count: counter.count + 1 })
+ },
+}
+```
+
+## Why _Pinia_
+
+Pinia is is the most similar English pronunciation of the word _pineapple_ in Spanish: _piña_. A pineapple is in reality a group of individual flowers that join together to create a multiple fruit. Similar to stores, each one is born individually, but they are all connected at the end. It's also a delicious tropical fruit indigenous to South America.
+
+## A more realistic example
+
+Here is a more complete example of the API you will be using with Pinia **with types** even in JavaScript:
+
+```js
+import { defineStore } from 'pinia'
+
+export const todos = defineStore({
+ id: 'todos',
+ state: () => ({
+ /** @type {{ text: string, id: number, isFinished: boolean }[]} */
+ todos: [],
+ /** @type {'all' | 'finished' | 'unfinished'} */
+ filter: 'all',
+ // type will be automatically inferred to number
+ nextId: 0,
+ }),
+ getters: {
+ finishedTodos() {
+ // autocompletion! ✨
+ return this.todos.filter((todo) => todo.isFinished)
+ },
+ unfinishedTodos() {
+ return this.todos.filter((todo) => !todo.isFinished)
+ },
+ filteredTodos() {
+ if (this.filter === 'finished') {
+ // call other getters with autocompletion ✨
+ return this.finishedTodos()
+ } else if (this.filter === 'unfinished') {
+ return this.unfinishedTodos()
+ }
+ return this.todos
+ },
+ },
+ actions: {
+ // any amount of arguments, return a promise or not
+ addTodo(text) {
+ // you can directly mutate the state
+ this.todos.push({ text, id: nextId++, isFinished: false })
+ },
+ },
+})
+```