π Upgrade to Feathers-Pinia 2.0 π
Feathers-Pinia 2.0 is almost ready for final release. Read the new documentation.π§ Migration from Vuex β€ 4 π§
π§ This page is a work in progress. If you see something that needs more clarity, please open an issue. π§
Despite the structure of Vuex and Pinia stores being different, a lot of the logic can be re-used. This guide serves to outline the basic steps required for migrating a project using Vuex and FeathersVuex to Pinia and FeathersPinia.
TIP ABOUT VUEX VERSIONS
Vuex 3.x is for Vue 2
Vuex 4.x is for Vue 3
The sooner you migrate away from Vuex, the sooner you'll enjoy massive speed and development improvements, and Feathers-Pinia makes it so much nicer.
File Structure
The following structure (adapted from the pinia official documentation) shows how to migrate from modules, to the more decentralized pinia stores and how to organize them, alongside those augmented with feathers-pinia.
# Vuex example (assuming namespaced modules)
src
βββ store
βββ index.js # Initializes Vuex, imports modules
βββ modules
βββ module1.js # 'module1' namespace
βββ nested
βββ index.js # 'nested' namespace, imports module2 & module3
βββ module2.js # 'nested/module2' namespace
βββ module3.js # 'nested/module3' namespace
# Pinia equivalent, note ids match previous namespaces
src
βββ stores
βββ index.js # (Optional) Initializes Pinia, does not import stores
βββ module1.js # 'module1' id
βββ module2.js # 'module2' id
βββ store.js # (Optional) Initializes Pinia, does not import stores
βββ service1.js # 'service1' id
βββ service2.js # 'service2' id
Re-exporting stores from a single file
Pinia is modular and composable by design, however, this makes it cumbersome to import the individual (and independent) stores when needed. The optional src/stores/index.js
file is for convenience to import stores from a single file:
import { useStore1, useStore2, useStore3 } from '@/stores'
instead of:
import { useStore1 } from '@/stores/store-1'
import { useStore2 } from '@/stores/store-2'
import { useStore3 } from '@/stores/store-3'
and could look like this:
export * from './store-1'
export * from './store-2'
export * from './store-3'
Note also that re-exporting files from a single file like shown above should not break code splitting.
Setup
For a more detailed walkthrough refer to the setup guide.
// src/stores/store.js
import { createPinia } from 'pinia'
import { setupFeathersPinia } from 'feathers-pinia'
import { api } from '@/feathers'
export const pinia = createPinia()
export const { defineStore, BaseModel } = setupFeathersPinia({ clients: { api } })
Usage
Stores in pinia should be imported whenever they are to be used. You don't have to worry about performance optimizations, pinia does this under-the-hood for you.
Usage in Components (.vue)
Within components, you can instantiate stores directly with const store = useStore()
.
// src/stores/todos.js
import { pinia } from 'pinia'
import { defineStore, BaseModel } from './store.js'
// avoid exporting Model to prevent accessing it directly (enforce access via store.Model)
class Todo extends BaseModel {}
const servicePath = 'todos'
const useTodosStore = defineStore('todos', {
servicePath,
model: Todo,
state: () => ({...}),
getters: {...},
actions: {...}
})
const todosStore = useTodosStore(pinia)
if (import.meta.env.PROD)
syncWithStorageCompressed(todosStore, ['itemsById', 'tempsById'])
else
syncWithStorage(todosStore, ['itemsById', 'tempsById'])
if (import.meta.hot)
import.meta.hot.accept(acceptHMRUpdate(useTodosStore as any, import.meta.hot))
Usage outside of components (.js or .ts)
Outside of components pinia might not be registered yet, so instantiating stores require binding the pinia instance created above with const store = useStore(pinia)
. This is especially problematic with Vue 2 which relies on Vue.use()
and the order of the ES imports/exports.
// src/stores/todos.js
import { pinia } from 'pinia'
import { defineStore, BaseModel } from 'feathers-pinia'
// avoid export Model to prevent accessing it directly and enforce access through store.Model
class Todo extends BaseModel {}
const servicePath = 'todos'
// could also directly provide the id within the options
// e.g. defineStore({ id: 'todos', ... }) or skip it and let
// feathers-pinia apply the default `service.${servicePath}`
const useTodosStore = defineStore('todos', {
servicePath,
model: Todo,
state: () => ({}),
getters: {},
actions: {}
})
Important: feathers-pinia will default to an id of service.${servicePath}
if no id is provided to defineStore
. This makes it easy to distinguish regular pinia stores from those created with feathers-pinia.
Steps
If you haven't completed the initial setup, please do so first.
The following steps outline the specific migration steps to apply to your codebase. The list might not be exhaustive, be sure to apply these concepts to any custom implmentation or variation you might have.
Imports
- npm: (optionally) uninstall
vuex
andfeathers-vuex
and installpinia
andfeathers-pinia
- imports: find and replace all
from 'feathers-vuex'
withfrom 'feathers-pinia'
Breaking Changes
- Add model instances to store after intantiation with
instance.addToStore()
(see here) - Find and replace state, getters and actions according to this list.
Stores
Make sure you import and create store beforehand with const <id>Store = use<id>Store
- state: find and replace all
$store.state.<id>.<stateName>
with<id>Store.<stateName>
- getters: find and replace all
$store.state.<id>.<getterName>
with<id>Store.<getterName>
- mutations: find and remove all mutations and invocations via
$store.commit
. Replace with actions if needed. - actions: find and replace all
$store.dispatch('<id>/<actionName>', payload)
with<id>Store.<actionName>(payload)
- Now apply the aforementioned steps by replacing
$store
withstore
(without$
). - computed props from state: find and replace any object computed properties e.g.
const doneTodos = computed({ get: () => $store.state.todos.doneTodos, set: (val) => $store.commit('todos/SET_DONE_TODOS', val) })
simply by accessin the reactive store directly viatodosStore.doneTodos
(and add this property to the state of the store). The same is true for computed props from getters. - ModelClass: find and replace all direct access via
ModelClass
withstore.Model
. Note you could still use e.g.const ModelClass = models.api[modelName]
, however store must be first initialized. Accessing the Model indirectly through the store ensures the store is previously instantiated.
Common Tools
useFind
: has been renamed touseFindWatched
. The API is the same. (See here)useGet
: has been renamed touseGetWatched
. The API is the same. (See here)- usePagination: (optional) replace custom implementations of pagination with the
usePagination
utility - handleClones: the new
handleClones
utility removes boilerplate from the clone and commit pattern (see here)
Renderless components
- If you where using renderless components
<feathers-vuex-form-wrapper>
and/or<feathers-vuex-form-input>
, you can either create 2 custom components to recreate that functionality or use the recommendedhandleClones
utility.
Tip: you can use of the find-and-replace functionality in the IDE of your choice to make this easier.
Compared to Feathers-Vuex
Apart from being a LOT faster and having a really clean API (thanks to Pinia), there are a few breaking changes to some of the familiar processes from Feathers-Vuex.
New Model Instances Not Added to the store
With Feathers-Vuex, when you called new Model(data)
, the new instance would automatically get added to the store.
In Feathers-Pinia, you have to call instance.addToStore()
to manually add the instance to the store.
import { models } from 'feathers-pinia'
const todo = new models.api.Todo({ name: 'do something' })
todo.addToStore()
Calling .clone() on a clone is allowed
In Feathers-Vuex you couldn't call .clone()
on a clone. Now, calling .clone()
will do the same as calling .reset()
.
Store Structure Changes
Since state
, getters
, and actions
all live inside the same Pinia store object, the getters have been renamed to avoid colliding with action names.
State
ids
is no longer in state. It's now a getter nameditemIds
.keyedById
is now calleditemsById
.
Getters
findInStore
takes the place of thefind
getter.countInStore
takes the place of thecount
getter.getFromStore
takes the place of theget
getter.itemIds
takes the place of theids
array in data.items
takes the place of thelist
getter.tempIds
is likeitemIds
but for temp records.temps
is a new array of temp records.cloneIds
is likeitemIds
but for clone records.clones
is a new array of clone records.
Actions Mostly the Same
find
count
get
create
update
patch
remove
removeFromStore
addOrUpdate
addToStore
is a new alias toaddOrUpdate
clearAll
clone
commit
reset
hydrateAll
might be deprecated. Hydration with Pinia is as simple asObject.assign(store, data)
.