All Projects → posva → Pinia

posva / Pinia

Licence: mit
🍍 Intuitive, type safe, light and flexible Store for Vue using the composition api with DevTools support

Programming Languages

typescript
32286 projects

Projects that are alternatives of or similar to Pinia

Nuxt Ssr
✨vue+nuxt+sass+node+express+MongoDB 实现的SSR项目。
Stars: ✭ 323 (-70.93%)
Mutual labels:  vuex, ssr
Nuxt Firebase Sns Example
Nuxt v2 & Firebase(Hosting / Functions SSR / Firestore), Google Auth SNS Example.
Stars: ✭ 485 (-56.35%)
Mutual labels:  vuex, ssr
Beauty
👙 VUE + VUEX + FIREBASE + BULMA … 实现的 SPA SSR 同构项目 - Tinder美女/豆瓣美女合集
Stars: ✭ 433 (-61.03%)
Mutual labels:  vuex, ssr
Vuex Easy Firestore
Easy coupling of firestore and a vuex module. 2-way sync with 0 boilerplate!
Stars: ✭ 224 (-79.84%)
Mutual labels:  vuex, store
Vue Meteor
🌠 Vue first-class integration in Meteor
Stars: ✭ 893 (-19.62%)
Mutual labels:  vuex, ssr
Vuex Mock Store
✅Simple and straightforward Vuex Store mock for vue-test-utils
Stars: ✭ 246 (-77.86%)
Mutual labels:  vuex, store
Svelte Store Router
Store-based router for Svelte
Stars: ✭ 54 (-95.14%)
Mutual labels:  store, ssr
Symfony Vuejs
Source code of the tutorial "Building a single-page application with Symfony 4 and Vue.js"
Stars: ✭ 170 (-84.7%)
Mutual labels:  vuex, store
Hackernews
HackerNews clone built with Nuxt.js
Stars: ✭ 758 (-31.77%)
Mutual labels:  vuex, ssr
Vuecnodejs
⚽️🎉Vue初/中级项目,CnodeJS社区重构。( a junior project of Vue.js, rewrite cnodejs.org ) 预览(DEMO):
Stars: ✭ 705 (-36.54%)
Mutual labels:  vuex, ssr
Vue Gates
🔒 A Vue.js & Nuxt.js plugin that allows you to use roles and permissions in your components or DOM elements, also compatible as middleware and methods.
Stars: ✭ 184 (-83.44%)
Mutual labels:  vuex, ssr
Koa Vue Ssr Template
This template built with vue 2.x, vue-router & vuex & webpack3 with server-side rendering by koa
Stars: ✭ 53 (-95.23%)
Mutual labels:  vuex, ssr
Mmf Blog Vue2 Ssr
mmf-blog-vue2 ssr(The service side rendering)
Stars: ✭ 174 (-84.34%)
Mutual labels:  vuex, ssr
Laravel Vuejs.com
Laravel and VueJs Blog, using Laravel nova, GraphQL, NuxtJs, Apollo and ...more
Stars: ✭ 54 (-95.14%)
Mutual labels:  vuex, ssr
Vue2 Demo
Vue 基于 Genesis + TS + Vuex 实现的 SSR demo
Stars: ✭ 2,072 (+86.5%)
Mutual labels:  vuex, ssr
Blog Client
使用vue全家桶制作的博客前台页面
Stars: ✭ 443 (-60.13%)
Mutual labels:  vuex, ssr
Egg Vue Webpack Boilerplate
Egg Vue Server Side Render (SSR) / Client Side Render (CSR)
Stars: ✭ 1,302 (+17.19%)
Mutual labels:  vuex, ssr
Surmon.me
🆒 My personal website and blog, powered by @vuejs (3)
Stars: ✭ 1,767 (+59.05%)
Mutual labels:  vuex, ssr
Nuepress
📖 Nuxt.js + WordPress REST API
Stars: ✭ 524 (-52.84%)
Mutual labels:  vuex, ssr
Nuxt.js
The Intuitive Vue(2) Framework
Stars: ✭ 38,986 (+3409.09%)
Mutual labels:  vuex, ssr

Pinia logo


npm package build status code coverage


Pinia

Intuitive, type safe and flexible Store for Vue

  • 💡 Intuitive
  • 🔑 Type Safe
  • ⚙️ Devtools support
  • 🔌 Extensible
  • 🏗 Modular by design
  • 📦 Extremely light

Pinia works both for Vue 2.x and Vue 3.x and you are currently on the branch that supports Vue 3.x. If you are looking for the version compatible with Vue 2.x, check the v1 branch.

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.

👉 Demo on CodeSandbox

Help me keep working on this project 💚

Gold Sponsors

Passionate People

Silver Sponsors

Vue Mastery Vuetify CodeStream

Bronze Sponsors

Storyblok Storyblok


FAQ

A few notes about the project and possible questions:

Q: Does this replace Vuex, is it its successor?

A: No, or at least that's not the main intention

Q: What about dynamic modules?

A: Dynamic modules are not type safe, so instead we allow creating different stores that can be imported anywhere

Roadmap / Ideas

  • [x] Should the state be merged at the same level as actions and getters?
  • [ ] Allow grouping stores together into a similar structure and allow defining new getters (pinia) You can directly call useOtherStore() inside of a getter or action.
  • [ ] Getter with params that act like computed properties (@ktsn)

Installation

yarn add [email protected]
# or with npm
npm install [email protected]

Usage

Install the plugin

Create a pinia (the root store) and pass it to app:

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:

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:

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:

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:

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.

You can access any property defined in state and getters directly on the store, similar to data and computed properties in a Vue component.

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:

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:

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:

main.counter++

or call the method $patch that allows you apply multiple changes at the same time with a partial state object:

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:

main.$state = { counter: 666, name: 'Paimon' }

SSR

Creating stores with Pinia should work out of the box for SSR as long as you call your useStore() functions at the top of setup functions, getters and actions:

export default defineComponent({
  setup() {
    // this works because pinia knows what application is running
    const main = useMainStore()
    return { main }
  },
})

If you need to use the store somewhere else, you need to pass the pinia instance that was passed to the app to the useStore() function call:

const pinia = createPinia()
const app = createApp(App)

app.use(router)
app.use(pinia)

router.beforeEach((to) => {
  // ✅ This will work make sure the correct store is used for the current running app
  const main = useMainStore(pinia)

  if (to.meta.requiresAuth && !main.isLoggedIn) return '/login'
})

To hydrate the initial state, you need to make sure the rootState is included somewhere in the HTML for Pinia to pick it up later on:

import { createPinia } from 'pinia'
// retrieve the rootState server side
const pinia = createPinia()
const app = createApp(App)
app.use(router)
app.use(pinia)

// after rendering the page, the root state is build and can be read
// serialize, escape (VERY important if the content of the state can be changed
// by the user, which is almost always the case), and place it somewhere on
// the page, for example, as a global variable. Note you need to use your own
// `escapeHTML()` function or use an existing package
escapeHTML(JSON.stringify(pinia.state.value))

On client side, you must hydrate pinia's state before calling any useStore() function. For example, if we serialize the state into a <script> tag to make it accessible globally on client side through window.__pinia, we can write this:

const pinia = createPinia()
const app = createApp(App)
app.use(pinia)

// must be set by the user
if (isClient) {
  pinia.state.value = JSON.parse(window.__pinia)
}

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:

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:

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:

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)
      }
    },
  },
})

Plugins

TODO: pinia.use() + interface PiniaCustomProperties for TS

Subscribing to changes

TODO: store.$subscribe()

Related

License

MIT

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].