SlideShare a Scribd company logo
Vuex to Pinia
How to migrate an existing app from Vuex to Pinia?
- Denny Biasiolli -
1
WHO AM I
Denny Biasiolli
Full Stack Developer
(JavaScript, Python, Go)
Front End Developer UX/ UI
Fingerprint Supervision Ltd
Italy, Savigliano (CN)
@dennybiasiolli
denny.biasiolli@gmail.com
www.dennybiasiolli.com
2
WHAT IS PINIA?
Vuex 5 Pinia is the officially recognized
state management library for Vue.
Started as an experiment to redesign what a Store for
Vue could look like with the Composition API
https://pinia.vuejs.org/introduction.html
3
WHAT IS PINIA?
Started out as an exploration of what the next iteration
of Vuex could look like, incorporating many ideas from
core team discussions for Vuex 5.
Pinia already implemented most of what they wanted
in Vuex 5 and decided to make it the new
recommendation instead.
https://pinia.vuejs.org/introduction.html#comparison-with-vuex
4
COMPARISON WITH VUEX
5
(SMALL) COMPARISON WITH VUEX
3.X/4.X
Pinia works with Vue 2 and Vue 3
Simpler API than Vuex
Mutations no longer exist, o en perceived as
extremely verbose.
No need to create custom complex wrappers to
support TypeScript
https://pinia.vuejs.org/introduction.html#comparison-with-vuex
6
(SMALL) COMPARISON WITH VUEX
3.X/4.X
No more magic strings to inject
Import the functions, call them, enjoy
autocompletion!
No need to dynamically add stores
They are all dynamic by default.
https://pinia.vuejs.org/introduction.html#comparison-with-vuex
7
(SMALL) COMPARISON WITH VUEX
3.X/4.X
No more nested structuring of modules.
You can still use a store inside another.
No namespaced modules.
You could say all stores are namespaced.
https://pinia.vuejs.org/introduction.html#comparison-with-vuex
8
INSTALL PINIA
Pinia can coexist with Vuex
Vue 3.x and 2.7.x
Vue 2.6.x
npm install -S pinia
npm install -S @vue/composition-api
npm install -S pinia
https://pinia.vuejs.org/getting-started.html#installation
9
ROOT STORE (BASIC)
Vue 3.x
Vue 2.x
// src/main.js
import { createPinia } from 'pinia'
app.use(createPinia())
// src/main.js
import { createPinia, PiniaVuePlugin } from 'pinia'
Vue.use(PiniaVuePlugin)
const pinia = createPinia()
new Vue({
// other options (store, render function, etc)...
pinia,
})
10
ROOT STORE (ADVANCED)
Vue 3.x
Vue 2.x
// src/stores/index.js
import { createPinia } from 'pinia'
export default createPinia()
// src/stores/index.js
import { createPinia, PiniaVuePlugin } from 'pinia'
import Vue from 'vue'
Vue.use(PiniaVuePlugin)
export default createPinia()
11
ROOT STORE (ADVANCED)
Vue 3.x
Vue 2.x
// src/main.js
import pinia from './stores'
app.use(pinia)
// src/main.js
import pinia from './stores'
new Vue({
// other options (store, render function, etc)...
pinia,
})
12
DEFINING A STORE
Vue 3.x/2.x
// src/stores/main.js
import { defineStore } from 'pinia'
// the first argument is a unique id of the store
export const useStore = defineStore('main', {
state: () => ({
count: 0,
}),
getters: {
isEven: state => state.count % 2 === 0,
isOdd() {
return !this.isEven
},
},
13
DEFINING A STORE
Vue 3.x/2.x
// src/stores/main.js
import { defineStore } from 'pinia'
export const useStore = defineStore('main', {
// ..
actions: {
increment() {
this.counter++
},
},
})
14
USING THE STORE
Vue 3.x
// src/components/Component.js
import { computed } from 'vue'
import { storeToRefs } from 'pinia'
import { useStore } from '@/stores/main'
const store = useStore() // direct usage of state/actions
const count = computed(() => store.count)
const isEven = computed(() => store.isEven)
const isOdd = computed(() => store.isOdd)
// or
const { count, isEven, isOdd } = storeToRefs(store)
// actions
const { increment } = store
15
USING THE STORE
Vue 2.x
// src/components/Component.js
import { mapState, mapActions } from 'pinia'
import { useStore } from '@/stores/main'
// computed section
...mapState(useStore, ['count', 'isEven', 'isOdd']),
...mapState(useStore, {
count: store => store.count,
isEven: store => store.isEven,
isOdd: store => store.isOdd,
}),
// methods section
...mapActions(useStore, ['increment']),
...mapActions(useStore, {
increment: 'increment',
}),
16
17
PREPARING THE MIGRATION
Vuex store structure
src
└── store
├── index.js # Vuex init, imports modules, main store
└── modules
├── todo.js # 'todo' namespace
└── todo-types.js
18
Vuex store definition
// src/store/index.js
export const defaultState = { /* ... */ }
export const getters = { /* ... */ }
export const mutations = { /* ... */ }
export const actions = { /* ... */ }
export const modules = { /* ... */ }
export default new Vuex.Store({
state: () => ({ ...defaultState }), // Vue 3.x + Vuex 4.x
// or
state: { ...defaultState }, // Vue 2.x + Vuex 3.x
getters, mutations, actions, modules,
})
19
Vuex store module
// src/store/modules/todo.js
export const defaultState = { /* ... */ }
export const getters = { /* ... */ }
export const mutations = { /* ... */ }
export const actions = { /* ... */ }
export default {
state: () => ({ ...defaultState }), // Vue 3.x + Vuex 4.x
// or
state: { ...defaultState }, // Vue 2.x + Vuex 3.x
getters, mutations, actions,
namespaced: true,
}
20
MIGRATE STORE DEFINITION
--- a/src/store/index.js
+++ b/src/stores/main.js
-import { createStore } from 'vuex'
+import { defineStore } from 'pinia'
-export default createStore({
+export const useStore = defineStore('main', {
- state: () => ({ ...defaultState }),
+ state: () => defaultState,
getters,
- mutations,
actions,
- modules,
})
21
MIGRATE STORE GETTERS
State? No changes!
--- a/src/store/index.js
+++ b/src/stores/main.js
export const getters = {
isEven: state => state.count % 2 === 0,
- isOdd(state, getters) {
- return !getters.isEven
+ isOdd() {
+ return !this.isEven
},
}
22
MIGRATE STORE MUTATIONS
mutations → actions
--- a/src/store/index.js
+++ b/src/stores/main.js
-export const mutations = {
+export const actions = {
- increment(state, payload) {
+ increment(payload) { // or multiple args
- state.count++
+ this.count++
},
}
23
MIGRATE STORE ACTIONS
--- a/src/store/index.js
+++ b/src/stores/main.js
export const actions = {
- incrementAsync({ commit, dispatch, state }, payload) {
+ incrementAsync(payload) { // or multiple args
setTimeout(() => {
+ // use `this` instead of `commit, dispatch, state`
- commit('increment')
+ this.increment()
}, 1000)
},
}
24
MAIN STORE MIGRATION
25
TODO MODULE/STORE MIGRATION
26
WHAT ABOUT TESTS?
27
TEST MIGRATION
state: no changes
getters
use .call on a getter when using this to access
other getters
-expect(getters.isOdd({}, { isEven: false })).toBe(true)
+expect(getters.isOdd.call({ isEven: false })).toBe(true)
28
TEST MIGRATION
mutations: → actions
actions
use .call to pass state/getters/actions
-const context = { commit: vi.fn() }
-actions.incrementAsync(context)
+const state = { increment: vi.fn() }
+actions.incrementAsync.call(state)
// ...
-expect(context.commit).toHaveBeenCalled()
+expect(state.increment).toHaveBeenCalled()
29
STATE TEST MIGRATION
import { test, expect } from 'vitest'
import { defaultState } from '@/stores/main'
test('defaultState should be as expected', () => {
expect(defaultState).toEqual({
count: 0,
})
})
30
GETTERS TEST MIGRATION
import { test, expect } from 'vitest'
import { getters } from '@/stores/main'
test('isEven should work as expected', () => {
expect(getters.isEven({ count: 0 })).toBe(true)
expect(getters.isEven({ count: 1 })).toBe(false)
})
test('isOdd should work as expected', () => {
- expect(getters.isOdd(undefined, { isEven: true }))
+ expect(getters.isOdd.call({ isEven: true }))
.toBe(false)
- expect(getters.isOdd(undefined, { isEven: false }))
+ expect(getters.isOdd.call({ isEven: false }))
.toBe(true)
})
31
MUTATIONS TEST MIGRATION
import { test, expect } from 'vitest'
-import { mutations } from '@/store/index'
+import { actions } from '@/stores/main'
test('increment should work as expected', () => {
const state = { count: 0 }
- mutations.increment(state)
+ actions.increment.call(state)
expect(state.count).toBe(1)
- mutations.increment(state)
+ actions.increment.call(state)
expect(state.count).toBe(2)
})
32
ACTIONS TEST MIGRATION
import { test, expect, vi } from 'vitest'
-import { actions } from '@/store/index'
+import { actions } from '@/stores/main'
test('incrementAsync should work as expected', () => {
vi.useFakeTimers()
- const context = { commit: vi.fn() }
- actions.incrementAsync(context)
+ const state = { increment: vi.fn() }
+ actions.incrementAsync.call(state)
- expect(context.commit).not.toHaveBeenCalled()
+ expect(state.increment).not.toHaveBeenCalled();
vi.advanceTimersByTime(1000)
- expect(context.commit).toHaveBeenCalledWith('increment')
+ expect(state.increment).toHaveBeenCalled();
vi.useRealTimers()
})
33
COMPONENT MIGRATION (VUE 3)
import { computed } from 'vue'
-import { useStore } from 'vuex'
+import { useStore } from '@/stores/main'
const store = useStore()
-const count = computed(() => store.state.count)
-const isEven = computed(() => store.getters.isEven)
-const isOdd = computed(() => store.getters.isOdd)
+const count = computed(() => store.count)
+const isEven = computed(() => store.isEven)
+const isOdd = computed(() => store.isOdd)
-const increment = () => store.commit('increment')
-const incrementAsync = () => store.dispatch('incrementAsync')
+const { increment, incrementAsync } = store
34
COMPONENT MIGRATION (VUE 3)
-import { computed } from 'vue'
+import { storeToRefs } from 'pinia'
-import { useStore } from 'vuex'
+import { useStore } from '@/stores/main'
const store = useStore()
-const count = computed(() => store.state.count)
-const isEven = computed(() => store.getters.isEven)
-const isOdd = computed(() => store.getters.isOdd)
+const { count, isEven, isOdd } = storeToRefs(store)
-const increment = () => store.commit('increment')
-const incrementAsync = () => store.dispatch('incrementAsync')
+const { increment, incrementAsync } = store
35
COMPONENT MIGRATION (VUE 2)
-import { mapState, mapGetters, mapMutations, mapActions } fro
+import { mapState, mapActions } from 'pinia'
+import { useStore } from '@/stores/main'
computed: {
- ...mapState(['count']),
- ...mapGetters(['isEven', 'isOdd']),
+ ...mapState(useStore, ['count', 'isEven', 'isOdd']),
},
methods: {
- ...mapMutations(['increment']),
- ...mapActions(['incrementAsync']),
+ ...mapActions(useStore, ['increment', 'incrementAsync']),
},
36
COMPONENT MIGRATION (VUE 2)
-import { createNamespacedHelpers } from 'vuex'
-import { GET_TODO_LIST } from '@/store/modules/todo-types'
-const { mapState, mapActions } = createNamespacedHelpers('tod
+import { mapState, mapActions } from 'pinia'
+import { useTodoStore } from '@/stores/todo'
computed: {
- ...mapState(['todoList']),
+ ...mapState(useTodoStore, ['todoList']),
},
mounted() {
- this[GET_TODO_LIST]()
+ this.getTodoList()
},
methods: {
- ...mapActions([GET_TODO_LIST]),
+ ...mapActions(useTodoStore, ['getTodoList']),
},
37
COMPONENT TESTING
@pinia/testing
npm install -D @pinia/testing
import { createTestingPinia } from '@pinia/testing'
createTestingPinia({
createSpy: vi.fn,
initialState: { main: { count: 0 } },
})
38
createTestingPinia
automatically mocks all actions
unit test store and components separately
allows you to overwrite getter values in tests
(not working with Vue 2 and Jest)
39
COMPONENT TESTING (VUE 3 + VITEST)
import { createTestingPinia } from '@pinia/testing'
import { shallowMount } from '@vue/test-utils';
import { useStore } from '@/stores/main'
let pinia, store
beforeEach(() => {
pinia = createTestingPinia({
createSpy: vi.fn,
initialState: { main: { count: 0 } },
})
// init store only after creating a testing pinia
store = useStore()
})
// tests
const wrapper = shallowMount(Component, {
global: { plugins: [pinia] }
})
40
COMPONENT TESTING (VUE 2 + JEST)
import { PiniaVuePlugin } from 'pinia'
import { createTestingPinia } from '@pinia/testing'
import { shallowMount, createLocalVue } from '@vue/test-utils'
import { useStore } from '@/stores/main'
const localVue = createLocalVue()
localVue.use(PiniaVuePlugin)
let pinia, store
beforeEach(() => {
pinia = createTestingPinia({
initialState: { main: { count: 0 } },
})
// init store only after creating a testing pinia
store = useStore()
})
// tests
const wrapper = shallowMount(Component, { localVue, pinia })
41
COMPONENT TESTING (VUE 2 + JEST)
Getters are not writable, you need to set the correct
state in order to make them work as expected.
store.count = 1
// or
store.$patch({
count: 1,
// other properties
})
42
COMPONENT TEST MIGRATION (VUE 3 + VITEST)
+import { createTestingPinia } from '@pinia/testing'
import { describe, test, beforeEach, expect, vi } from 'vites
-import { createStore } from 'vuex'
import { shallowMount } from '@vue/test-utils';
import Counter from '@/components/Counter.vue'
+import { useStore } from '@/stores/main'
43
COMPONENT TEST MIGRATION (VUE 3 + VITEST)
-let store
+let pinia, store
-let mutations = { increment: vi.fn() }
-let actions = { incrementAsync: vi.fn() }
beforeEach(() => {
- store = createStore({
- state: () => ({ count: 0 }),
- getters: { isEven: () => true, isOdd: () => false },
- mutations,
- actions,
- })
+ pinia = createTestingPinia({
+ createSpy: vi.fn,
+ initialState: { main: { count: 0 } },
+ })
+ store = useStore()
})
44
COMPONENT TEST MIGRATION (VUE 3 + VITEST)
test('should respect the snapshot', () => {
const wrapper = shallowMount(Counter, {
- global: { plugins: [store] }
+ global: { plugins: [pinia] }
})
expect(wrapper.element).toMatchSnapshot()
wrapper.findAll('button')[0].trigger('click')
- expect(mutations.increment).toHaveBeenCalled()
+ expect(store.increment).toHaveBeenCalled()
wrapper.findAll('button')[1].trigger('click')
- expect(actions.incrementAsync).toHaveBeenCalled()
+ expect(store.incrementAsync).toHaveBeenCalled()
})
45
COMPONENT TEST MIGRATION (VUE 2 + JEST)
-import Vuex from 'vuex'
+import { PiniaVuePlugin } from 'pinia'
+import { createTestingPinia } from '@pinia/testing'
import { shallowMount, createLocalVue } from '@vue/test-utils
import Counter from '@/components/Counter.vue'
+import { useStore } from '@/stores/main'
const localVue = createLocalVue()
-localVue.use(Vuex)
+localVue.use(PiniaVuePlugin)
46
COMPONENT TEST MIGRATION (VUE 2 + JEST)
-let store
+let pinia, store
-let mutations = { increment: jest.fn() }
-let actions = { incrementAsync: jest.fn() }
beforeEach(() => {
- store = createStore({
- state: { count: 0 },
- getters: { isEven: () => true, isOdd: () => false },
- mutations,
- actions,
- })
+ pinia = createTestingPinia({
+ initialState: { main: { count: 0 } },
+ })
+ store = useStore()
})
47
COMPONENT TEST MIGRATION (VUE 2 + JEST)
test('should respect the snapshot', () => {
const wrapper = shallowMount(Counter, {
- localVue, store,
+ localVue, pinia,
})
expect(wrapper.element).toMatchSnapshot()
wrapper.findAll('button')[0].trigger('click')
- expect(mutations.increment).toHaveBeenCalled()
+ expect(store.increment).toHaveBeenCalled()
wrapper.findAll('button')[1].trigger('click')
- expect(actions.incrementAsync).toHaveBeenCalled()
+ expect(store.incrementAsync).toHaveBeenCalled()
})
48
EVRYTHING SHOULD BE FINE, RIGHT?
49
MIGRATION PROBLEMS NOTES
Direct $store usage
Use the correct store
$store.state.propertyName
$store.state.moduleName.propertyName
$store.getters.getterName
$store.getters['moduleName/getterName']
const myStore = useMyStore()
myStore.propertyName
myStore.getterName
50
MIGRATION PROBLEMS NOTES
Direct $store commit/dispatch
Use store actions
$store.commit.mutationName()
$store.commit.moduleName.mutationName()
$store.dispatch.actionName()
$store.dispatch['moduleName/actionName']()
const myStore = useMyStore()
myStore.actionName()
51
MIGRATION PROBLEMS NOTES
What about a global useStore()?
If used outside of <script setup>,
will give you this error
import { useStore } from '@/stores/main'
const store = useStore()
// other stuff
getActivePinia was called with no active Pinia.
Did you forget to install pinia?
52
MIGRATION PROBLEMS NOTES
Can Vuex and Pinia coexist?
Yes, but...
make sure to migrate entire modules,
not entire components.
53
MIGRATION PROBLEMS NOTES
How to test a real store?
import { setActivePinia, createPinia } from 'pinia'
beforeEach(() => {
setActivePinia(createPinia())
})
test('should work as expected', () => {
const store = useStore()
// ...
})
54
MIGRATION PROBLEMS NOTES
What about store persistence?
myStore.$subscribe((mutation, state) => {
localStorage.setItem('myStore', JSON.stringify(state))
})
// restore with
myStore.$state = { /* ... */ }
watch(
pinia.state,
(state) => {
localStorage.setItem('piniaState', JSON.stringify(state))
},
{ deep: true }
)
// restore with
pinia.state.value = { /* ... */ }
https://pinia.vuejs.org/core-concepts/state.html#subscribing-to-the-state
55
FINAL TASKS
1. remove Vuex store from main.js
-import store from './store'
// Vue 3
-app.use(store)
// Vue 2
new Vue({
router,
- store,
pinia,
render: h => h(App),
}).$mount('#app')
56
FINAL TASKS
2. delete Vuex store and tests
3. uninstall Vuex dependencies
rm -rf src/store
rm -rf tests/unit/store
npm uninstall vuex @vue/cli-plugin-vuex
57
LINKS
(moving-to-pinia branch)
@dennybiasiolli
pinia.vuejs.org
github.com/dennybiasiolli/vuex-to-pinia-vue3
github.com/dennybiasiolli/vuex-to-pinia-vue2
www.dennybiasiolli.com
58

More Related Content

Similar to Vuex to Pinia, how to migrate an existing app

How to build to do app using vue composition api and vuex 4 with typescript
How to build to do app using vue composition api and vuex 4 with typescriptHow to build to do app using vue composition api and vuex 4 with typescript
How to build to do app using vue composition api and vuex 4 with typescript
Katy Slemon
 
Vuex
VuexVuex
Vue business first
Vue business firstVue business first
Vue business first
Vitalii Ratyshnyi
 
Vue js and Dyploma
Vue js and DyplomaVue js and Dyploma
Vue js and Dyploma
Yoram Kornatzky
 
Yves & Zed @ Developer Conference 2013
Yves & Zed @ Developer Conference 2013Yves & Zed @ Developer Conference 2013
Yves & Zed @ Developer Conference 2013
FabianWesnerBerlin
 
React redux
React reduxReact redux
React redux
Michel Perez
 
Pablo Magaz | ECMAScript 2018 y más allá | Codemotion Madrid 2018
Pablo Magaz | ECMAScript 2018 y más allá | Codemotion Madrid 2018Pablo Magaz | ECMAScript 2018 y más allá | Codemotion Madrid 2018
Pablo Magaz | ECMAScript 2018 y más allá | Codemotion Madrid 2018
Codemotion
 
Building and deploying React applications
Building and deploying React applicationsBuilding and deploying React applications
Building and deploying React applications
Astrails
 
React lecture
React lectureReact lecture
React lecture
Christoffer Noring
 
React + Redux. Best practices
React + Redux.  Best practicesReact + Redux.  Best practices
React + Redux. Best practices
Clickky
 
Gutenberg sous le capot, modules réutilisables
Gutenberg sous le capot, modules réutilisablesGutenberg sous le capot, modules réutilisables
Gutenberg sous le capot, modules réutilisables
Riad Benguella
 
Meet VueJs
Meet VueJsMeet VueJs
Meet VueJs
Mathieu Breton
 
20150516 modern web_conf_tw
20150516 modern web_conf_tw20150516 modern web_conf_tw
20150516 modern web_conf_tw
Tse-Ching Ho
 
Building Universal Web Apps with React ForwardJS 2017
Building Universal Web Apps with React ForwardJS 2017Building Universal Web Apps with React ForwardJS 2017
Building Universal Web Apps with React ForwardJS 2017
Elyse Kolker Gordon
 
Rails 6 frontend frameworks
Rails 6 frontend frameworksRails 6 frontend frameworks
Rails 6 frontend frameworks
Eric Guo
 
Relay Modern: architecture and workflow
Relay Modern: architecture and workflowRelay Modern: architecture and workflow
Relay Modern: architecture and workflow
Alex Alexeev
 
using Mithril.js + postgREST to build and consume API's
using Mithril.js + postgREST to build and consume API'susing Mithril.js + postgREST to build and consume API's
using Mithril.js + postgREST to build and consume API's
Antônio Roberto Silva
 
ApplicationCoordinator для навигации между экранами / Павел Гуров (Avito)
ApplicationCoordinator для навигации между экранами / Павел Гуров (Avito)ApplicationCoordinator для навигации между экранами / Павел Гуров (Avito)
ApplicationCoordinator для навигации между экранами / Павел Гуров (Avito)
Ontico
 
Beginner’s tutorial (part 2) how to integrate redux-saga in react native app
Beginner’s tutorial (part 2) how to integrate redux-saga in react native appBeginner’s tutorial (part 2) how to integrate redux-saga in react native app
Beginner’s tutorial (part 2) how to integrate redux-saga in react native app
Katy Slemon
 
Vue fundamentasl with Testing and Vuex
Vue fundamentasl with Testing and VuexVue fundamentasl with Testing and Vuex
Vue fundamentasl with Testing and Vuex
Christoffer Noring
 

Similar to Vuex to Pinia, how to migrate an existing app (20)

How to build to do app using vue composition api and vuex 4 with typescript
How to build to do app using vue composition api and vuex 4 with typescriptHow to build to do app using vue composition api and vuex 4 with typescript
How to build to do app using vue composition api and vuex 4 with typescript
 
Vuex
VuexVuex
Vuex
 
Vue business first
Vue business firstVue business first
Vue business first
 
Vue js and Dyploma
Vue js and DyplomaVue js and Dyploma
Vue js and Dyploma
 
Yves & Zed @ Developer Conference 2013
Yves & Zed @ Developer Conference 2013Yves & Zed @ Developer Conference 2013
Yves & Zed @ Developer Conference 2013
 
React redux
React reduxReact redux
React redux
 
Pablo Magaz | ECMAScript 2018 y más allá | Codemotion Madrid 2018
Pablo Magaz | ECMAScript 2018 y más allá | Codemotion Madrid 2018Pablo Magaz | ECMAScript 2018 y más allá | Codemotion Madrid 2018
Pablo Magaz | ECMAScript 2018 y más allá | Codemotion Madrid 2018
 
Building and deploying React applications
Building and deploying React applicationsBuilding and deploying React applications
Building and deploying React applications
 
React lecture
React lectureReact lecture
React lecture
 
React + Redux. Best practices
React + Redux.  Best practicesReact + Redux.  Best practices
React + Redux. Best practices
 
Gutenberg sous le capot, modules réutilisables
Gutenberg sous le capot, modules réutilisablesGutenberg sous le capot, modules réutilisables
Gutenberg sous le capot, modules réutilisables
 
Meet VueJs
Meet VueJsMeet VueJs
Meet VueJs
 
20150516 modern web_conf_tw
20150516 modern web_conf_tw20150516 modern web_conf_tw
20150516 modern web_conf_tw
 
Building Universal Web Apps with React ForwardJS 2017
Building Universal Web Apps with React ForwardJS 2017Building Universal Web Apps with React ForwardJS 2017
Building Universal Web Apps with React ForwardJS 2017
 
Rails 6 frontend frameworks
Rails 6 frontend frameworksRails 6 frontend frameworks
Rails 6 frontend frameworks
 
Relay Modern: architecture and workflow
Relay Modern: architecture and workflowRelay Modern: architecture and workflow
Relay Modern: architecture and workflow
 
using Mithril.js + postgREST to build and consume API's
using Mithril.js + postgREST to build and consume API'susing Mithril.js + postgREST to build and consume API's
using Mithril.js + postgREST to build and consume API's
 
ApplicationCoordinator для навигации между экранами / Павел Гуров (Avito)
ApplicationCoordinator для навигации между экранами / Павел Гуров (Avito)ApplicationCoordinator для навигации между экранами / Павел Гуров (Avito)
ApplicationCoordinator для навигации между экранами / Павел Гуров (Avito)
 
Beginner’s tutorial (part 2) how to integrate redux-saga in react native app
Beginner’s tutorial (part 2) how to integrate redux-saga in react native appBeginner’s tutorial (part 2) how to integrate redux-saga in react native app
Beginner’s tutorial (part 2) how to integrate redux-saga in react native app
 
Vue fundamentasl with Testing and Vuex
Vue fundamentasl with Testing and VuexVue fundamentasl with Testing and Vuex
Vue fundamentasl with Testing and Vuex
 

Recently uploaded

42 Ways to Generate Real Estate Leads - Sellxpert
42 Ways to Generate Real Estate Leads - Sellxpert42 Ways to Generate Real Estate Leads - Sellxpert
42 Ways to Generate Real Estate Leads - Sellxpert
vaishalijagtap12
 
Boost Your Savings with These Money Management Apps
Boost Your Savings with These Money Management AppsBoost Your Savings with These Money Management Apps
Boost Your Savings with These Money Management Apps
Jhone kinadey
 
Orca: Nocode Graphical Editor for Container Orchestration
Orca: Nocode Graphical Editor for Container OrchestrationOrca: Nocode Graphical Editor for Container Orchestration
Orca: Nocode Graphical Editor for Container Orchestration
Pedro J. Molina
 
Enhanced Screen Flows UI/UX using SLDS with Tom Kitt
Enhanced Screen Flows UI/UX using SLDS with Tom KittEnhanced Screen Flows UI/UX using SLDS with Tom Kitt
Enhanced Screen Flows UI/UX using SLDS with Tom Kitt
Peter Caitens
 
Assure Contact Center Experiences for Your Customers With ThousandEyes
Assure Contact Center Experiences for Your Customers With ThousandEyesAssure Contact Center Experiences for Your Customers With ThousandEyes
Assure Contact Center Experiences for Your Customers With ThousandEyes
ThousandEyes
 
Beginner's Guide to Observability@Devoxx PL 2024
Beginner's  Guide to Observability@Devoxx PL 2024Beginner's  Guide to Observability@Devoxx PL 2024
Beginner's Guide to Observability@Devoxx PL 2024
michniczscribd
 
Unlock the Secrets to Effortless Video Creation with Invideo: Your Ultimate G...
Unlock the Secrets to Effortless Video Creation with Invideo: Your Ultimate G...Unlock the Secrets to Effortless Video Creation with Invideo: Your Ultimate G...
Unlock the Secrets to Effortless Video Creation with Invideo: Your Ultimate G...
The Third Creative Media
 
14 th Edition of International conference on computer vision
14 th Edition of International conference on computer vision14 th Edition of International conference on computer vision
14 th Edition of International conference on computer vision
ShulagnaSarkar2
 
Ensuring Efficiency and Speed with Practical Solutions for Clinical Operations
Ensuring Efficiency and Speed with Practical Solutions for Clinical OperationsEnsuring Efficiency and Speed with Practical Solutions for Clinical Operations
Ensuring Efficiency and Speed with Practical Solutions for Clinical Operations
OnePlan Solutions
 
Stork Product Overview: An AI-Powered Autonomous Delivery Fleet
Stork Product Overview: An AI-Powered Autonomous Delivery FleetStork Product Overview: An AI-Powered Autonomous Delivery Fleet
Stork Product Overview: An AI-Powered Autonomous Delivery Fleet
Vince Scalabrino
 
Safelyio Toolbox Talk Softwate & App (How To Digitize Safety Meetings)
Safelyio Toolbox Talk Softwate & App (How To Digitize Safety Meetings)Safelyio Toolbox Talk Softwate & App (How To Digitize Safety Meetings)
Safelyio Toolbox Talk Softwate & App (How To Digitize Safety Meetings)
safelyiotech
 
The Power of Visual Regression Testing_ Why It Is Critical for Enterprise App...
The Power of Visual Regression Testing_ Why It Is Critical for Enterprise App...The Power of Visual Regression Testing_ Why It Is Critical for Enterprise App...
The Power of Visual Regression Testing_ Why It Is Critical for Enterprise App...
kalichargn70th171
 
Penify - Let AI do the Documentation, you write the Code.
Penify - Let AI do the Documentation, you write the Code.Penify - Let AI do the Documentation, you write the Code.
Penify - Let AI do the Documentation, you write the Code.
KrishnaveniMohan1
 
Flutter vs. React Native: A Detailed Comparison for App Development in 2024
Flutter vs. React Native: A Detailed Comparison for App Development in 2024Flutter vs. React Native: A Detailed Comparison for App Development in 2024
Flutter vs. React Native: A Detailed Comparison for App Development in 2024
dhavalvaghelanectarb
 
一比一原版(sdsu毕业证书)圣地亚哥州立大学毕业证如何办理
一比一原版(sdsu毕业证书)圣地亚哥州立大学毕业证如何办理一比一原版(sdsu毕业证书)圣地亚哥州立大学毕业证如何办理
一比一原版(sdsu毕业证书)圣地亚哥州立大学毕业证如何办理
kgyxske
 
Streamlining End-to-End Testing Automation
Streamlining End-to-End Testing AutomationStreamlining End-to-End Testing Automation
Streamlining End-to-End Testing Automation
Anand Bagmar
 
ppt on the brain chip neuralink.pptx
ppt  on   the brain  chip neuralink.pptxppt  on   the brain  chip neuralink.pptx
ppt on the brain chip neuralink.pptx
Reetu63
 
如何办理(hull学位证书)英国赫尔大学毕业证硕士文凭原版一模一样
如何办理(hull学位证书)英国赫尔大学毕业证硕士文凭原版一模一样如何办理(hull学位证书)英国赫尔大学毕业证硕士文凭原版一模一样
如何办理(hull学位证书)英国赫尔大学毕业证硕士文凭原版一模一样
gapen1
 
The Rising Future of CPaaS in the Middle East 2024
The Rising Future of CPaaS in the Middle East 2024The Rising Future of CPaaS in the Middle East 2024
The Rising Future of CPaaS in the Middle East 2024
Yara Milbes
 
WMF 2024 - Unlocking the Future of Data Powering Next-Gen AI with Vector Data...
WMF 2024 - Unlocking the Future of Data Powering Next-Gen AI with Vector Data...WMF 2024 - Unlocking the Future of Data Powering Next-Gen AI with Vector Data...
WMF 2024 - Unlocking the Future of Data Powering Next-Gen AI with Vector Data...
Luigi Fugaro
 

Recently uploaded (20)

42 Ways to Generate Real Estate Leads - Sellxpert
42 Ways to Generate Real Estate Leads - Sellxpert42 Ways to Generate Real Estate Leads - Sellxpert
42 Ways to Generate Real Estate Leads - Sellxpert
 
Boost Your Savings with These Money Management Apps
Boost Your Savings with These Money Management AppsBoost Your Savings with These Money Management Apps
Boost Your Savings with These Money Management Apps
 
Orca: Nocode Graphical Editor for Container Orchestration
Orca: Nocode Graphical Editor for Container OrchestrationOrca: Nocode Graphical Editor for Container Orchestration
Orca: Nocode Graphical Editor for Container Orchestration
 
Enhanced Screen Flows UI/UX using SLDS with Tom Kitt
Enhanced Screen Flows UI/UX using SLDS with Tom KittEnhanced Screen Flows UI/UX using SLDS with Tom Kitt
Enhanced Screen Flows UI/UX using SLDS with Tom Kitt
 
Assure Contact Center Experiences for Your Customers With ThousandEyes
Assure Contact Center Experiences for Your Customers With ThousandEyesAssure Contact Center Experiences for Your Customers With ThousandEyes
Assure Contact Center Experiences for Your Customers With ThousandEyes
 
Beginner's Guide to Observability@Devoxx PL 2024
Beginner's  Guide to Observability@Devoxx PL 2024Beginner's  Guide to Observability@Devoxx PL 2024
Beginner's Guide to Observability@Devoxx PL 2024
 
Unlock the Secrets to Effortless Video Creation with Invideo: Your Ultimate G...
Unlock the Secrets to Effortless Video Creation with Invideo: Your Ultimate G...Unlock the Secrets to Effortless Video Creation with Invideo: Your Ultimate G...
Unlock the Secrets to Effortless Video Creation with Invideo: Your Ultimate G...
 
14 th Edition of International conference on computer vision
14 th Edition of International conference on computer vision14 th Edition of International conference on computer vision
14 th Edition of International conference on computer vision
 
Ensuring Efficiency and Speed with Practical Solutions for Clinical Operations
Ensuring Efficiency and Speed with Practical Solutions for Clinical OperationsEnsuring Efficiency and Speed with Practical Solutions for Clinical Operations
Ensuring Efficiency and Speed with Practical Solutions for Clinical Operations
 
Stork Product Overview: An AI-Powered Autonomous Delivery Fleet
Stork Product Overview: An AI-Powered Autonomous Delivery FleetStork Product Overview: An AI-Powered Autonomous Delivery Fleet
Stork Product Overview: An AI-Powered Autonomous Delivery Fleet
 
Safelyio Toolbox Talk Softwate & App (How To Digitize Safety Meetings)
Safelyio Toolbox Talk Softwate & App (How To Digitize Safety Meetings)Safelyio Toolbox Talk Softwate & App (How To Digitize Safety Meetings)
Safelyio Toolbox Talk Softwate & App (How To Digitize Safety Meetings)
 
The Power of Visual Regression Testing_ Why It Is Critical for Enterprise App...
The Power of Visual Regression Testing_ Why It Is Critical for Enterprise App...The Power of Visual Regression Testing_ Why It Is Critical for Enterprise App...
The Power of Visual Regression Testing_ Why It Is Critical for Enterprise App...
 
Penify - Let AI do the Documentation, you write the Code.
Penify - Let AI do the Documentation, you write the Code.Penify - Let AI do the Documentation, you write the Code.
Penify - Let AI do the Documentation, you write the Code.
 
Flutter vs. React Native: A Detailed Comparison for App Development in 2024
Flutter vs. React Native: A Detailed Comparison for App Development in 2024Flutter vs. React Native: A Detailed Comparison for App Development in 2024
Flutter vs. React Native: A Detailed Comparison for App Development in 2024
 
一比一原版(sdsu毕业证书)圣地亚哥州立大学毕业证如何办理
一比一原版(sdsu毕业证书)圣地亚哥州立大学毕业证如何办理一比一原版(sdsu毕业证书)圣地亚哥州立大学毕业证如何办理
一比一原版(sdsu毕业证书)圣地亚哥州立大学毕业证如何办理
 
Streamlining End-to-End Testing Automation
Streamlining End-to-End Testing AutomationStreamlining End-to-End Testing Automation
Streamlining End-to-End Testing Automation
 
ppt on the brain chip neuralink.pptx
ppt  on   the brain  chip neuralink.pptxppt  on   the brain  chip neuralink.pptx
ppt on the brain chip neuralink.pptx
 
如何办理(hull学位证书)英国赫尔大学毕业证硕士文凭原版一模一样
如何办理(hull学位证书)英国赫尔大学毕业证硕士文凭原版一模一样如何办理(hull学位证书)英国赫尔大学毕业证硕士文凭原版一模一样
如何办理(hull学位证书)英国赫尔大学毕业证硕士文凭原版一模一样
 
The Rising Future of CPaaS in the Middle East 2024
The Rising Future of CPaaS in the Middle East 2024The Rising Future of CPaaS in the Middle East 2024
The Rising Future of CPaaS in the Middle East 2024
 
WMF 2024 - Unlocking the Future of Data Powering Next-Gen AI with Vector Data...
WMF 2024 - Unlocking the Future of Data Powering Next-Gen AI with Vector Data...WMF 2024 - Unlocking the Future of Data Powering Next-Gen AI with Vector Data...
WMF 2024 - Unlocking the Future of Data Powering Next-Gen AI with Vector Data...
 

Vuex to Pinia, how to migrate an existing app

  • 1. Vuex to Pinia How to migrate an existing app from Vuex to Pinia? - Denny Biasiolli - 1
  • 2. WHO AM I Denny Biasiolli Full Stack Developer (JavaScript, Python, Go) Front End Developer UX/ UI Fingerprint Supervision Ltd Italy, Savigliano (CN) @dennybiasiolli denny.biasiolli@gmail.com www.dennybiasiolli.com 2
  • 3. WHAT IS PINIA? Vuex 5 Pinia is the officially recognized state management library for Vue. Started as an experiment to redesign what a Store for Vue could look like with the Composition API https://pinia.vuejs.org/introduction.html 3
  • 4. WHAT IS PINIA? Started out as an exploration of what the next iteration of Vuex could look like, incorporating many ideas from core team discussions for Vuex 5. Pinia already implemented most of what they wanted in Vuex 5 and decided to make it the new recommendation instead. https://pinia.vuejs.org/introduction.html#comparison-with-vuex 4
  • 6. (SMALL) COMPARISON WITH VUEX 3.X/4.X Pinia works with Vue 2 and Vue 3 Simpler API than Vuex Mutations no longer exist, o en perceived as extremely verbose. No need to create custom complex wrappers to support TypeScript https://pinia.vuejs.org/introduction.html#comparison-with-vuex 6
  • 7. (SMALL) COMPARISON WITH VUEX 3.X/4.X No more magic strings to inject Import the functions, call them, enjoy autocompletion! No need to dynamically add stores They are all dynamic by default. https://pinia.vuejs.org/introduction.html#comparison-with-vuex 7
  • 8. (SMALL) COMPARISON WITH VUEX 3.X/4.X No more nested structuring of modules. You can still use a store inside another. No namespaced modules. You could say all stores are namespaced. https://pinia.vuejs.org/introduction.html#comparison-with-vuex 8
  • 9. INSTALL PINIA Pinia can coexist with Vuex Vue 3.x and 2.7.x Vue 2.6.x npm install -S pinia npm install -S @vue/composition-api npm install -S pinia https://pinia.vuejs.org/getting-started.html#installation 9
  • 10. ROOT STORE (BASIC) Vue 3.x Vue 2.x // src/main.js import { createPinia } from 'pinia' app.use(createPinia()) // src/main.js import { createPinia, PiniaVuePlugin } from 'pinia' Vue.use(PiniaVuePlugin) const pinia = createPinia() new Vue({ // other options (store, render function, etc)... pinia, }) 10
  • 11. ROOT STORE (ADVANCED) Vue 3.x Vue 2.x // src/stores/index.js import { createPinia } from 'pinia' export default createPinia() // src/stores/index.js import { createPinia, PiniaVuePlugin } from 'pinia' import Vue from 'vue' Vue.use(PiniaVuePlugin) export default createPinia() 11
  • 12. ROOT STORE (ADVANCED) Vue 3.x Vue 2.x // src/main.js import pinia from './stores' app.use(pinia) // src/main.js import pinia from './stores' new Vue({ // other options (store, render function, etc)... pinia, }) 12
  • 13. DEFINING A STORE Vue 3.x/2.x // src/stores/main.js import { defineStore } from 'pinia' // the first argument is a unique id of the store export const useStore = defineStore('main', { state: () => ({ count: 0, }), getters: { isEven: state => state.count % 2 === 0, isOdd() { return !this.isEven }, }, 13
  • 14. DEFINING A STORE Vue 3.x/2.x // src/stores/main.js import { defineStore } from 'pinia' export const useStore = defineStore('main', { // .. actions: { increment() { this.counter++ }, }, }) 14
  • 15. USING THE STORE Vue 3.x // src/components/Component.js import { computed } from 'vue' import { storeToRefs } from 'pinia' import { useStore } from '@/stores/main' const store = useStore() // direct usage of state/actions const count = computed(() => store.count) const isEven = computed(() => store.isEven) const isOdd = computed(() => store.isOdd) // or const { count, isEven, isOdd } = storeToRefs(store) // actions const { increment } = store 15
  • 16. USING THE STORE Vue 2.x // src/components/Component.js import { mapState, mapActions } from 'pinia' import { useStore } from '@/stores/main' // computed section ...mapState(useStore, ['count', 'isEven', 'isOdd']), ...mapState(useStore, { count: store => store.count, isEven: store => store.isEven, isOdd: store => store.isOdd, }), // methods section ...mapActions(useStore, ['increment']), ...mapActions(useStore, { increment: 'increment', }), 16
  • 17. 17
  • 18. PREPARING THE MIGRATION Vuex store structure src └── store ├── index.js # Vuex init, imports modules, main store └── modules ├── todo.js # 'todo' namespace └── todo-types.js 18
  • 19. Vuex store definition // src/store/index.js export const defaultState = { /* ... */ } export const getters = { /* ... */ } export const mutations = { /* ... */ } export const actions = { /* ... */ } export const modules = { /* ... */ } export default new Vuex.Store({ state: () => ({ ...defaultState }), // Vue 3.x + Vuex 4.x // or state: { ...defaultState }, // Vue 2.x + Vuex 3.x getters, mutations, actions, modules, }) 19
  • 20. Vuex store module // src/store/modules/todo.js export const defaultState = { /* ... */ } export const getters = { /* ... */ } export const mutations = { /* ... */ } export const actions = { /* ... */ } export default { state: () => ({ ...defaultState }), // Vue 3.x + Vuex 4.x // or state: { ...defaultState }, // Vue 2.x + Vuex 3.x getters, mutations, actions, namespaced: true, } 20
  • 21. MIGRATE STORE DEFINITION --- a/src/store/index.js +++ b/src/stores/main.js -import { createStore } from 'vuex' +import { defineStore } from 'pinia' -export default createStore({ +export const useStore = defineStore('main', { - state: () => ({ ...defaultState }), + state: () => defaultState, getters, - mutations, actions, - modules, }) 21
  • 22. MIGRATE STORE GETTERS State? No changes! --- a/src/store/index.js +++ b/src/stores/main.js export const getters = { isEven: state => state.count % 2 === 0, - isOdd(state, getters) { - return !getters.isEven + isOdd() { + return !this.isEven }, } 22
  • 23. MIGRATE STORE MUTATIONS mutations → actions --- a/src/store/index.js +++ b/src/stores/main.js -export const mutations = { +export const actions = { - increment(state, payload) { + increment(payload) { // or multiple args - state.count++ + this.count++ }, } 23
  • 24. MIGRATE STORE ACTIONS --- a/src/store/index.js +++ b/src/stores/main.js export const actions = { - incrementAsync({ commit, dispatch, state }, payload) { + incrementAsync(payload) { // or multiple args setTimeout(() => { + // use `this` instead of `commit, dispatch, state` - commit('increment') + this.increment() }, 1000) }, } 24
  • 28. TEST MIGRATION state: no changes getters use .call on a getter when using this to access other getters -expect(getters.isOdd({}, { isEven: false })).toBe(true) +expect(getters.isOdd.call({ isEven: false })).toBe(true) 28
  • 29. TEST MIGRATION mutations: → actions actions use .call to pass state/getters/actions -const context = { commit: vi.fn() } -actions.incrementAsync(context) +const state = { increment: vi.fn() } +actions.incrementAsync.call(state) // ... -expect(context.commit).toHaveBeenCalled() +expect(state.increment).toHaveBeenCalled() 29
  • 30. STATE TEST MIGRATION import { test, expect } from 'vitest' import { defaultState } from '@/stores/main' test('defaultState should be as expected', () => { expect(defaultState).toEqual({ count: 0, }) }) 30
  • 31. GETTERS TEST MIGRATION import { test, expect } from 'vitest' import { getters } from '@/stores/main' test('isEven should work as expected', () => { expect(getters.isEven({ count: 0 })).toBe(true) expect(getters.isEven({ count: 1 })).toBe(false) }) test('isOdd should work as expected', () => { - expect(getters.isOdd(undefined, { isEven: true })) + expect(getters.isOdd.call({ isEven: true })) .toBe(false) - expect(getters.isOdd(undefined, { isEven: false })) + expect(getters.isOdd.call({ isEven: false })) .toBe(true) }) 31
  • 32. MUTATIONS TEST MIGRATION import { test, expect } from 'vitest' -import { mutations } from '@/store/index' +import { actions } from '@/stores/main' test('increment should work as expected', () => { const state = { count: 0 } - mutations.increment(state) + actions.increment.call(state) expect(state.count).toBe(1) - mutations.increment(state) + actions.increment.call(state) expect(state.count).toBe(2) }) 32
  • 33. ACTIONS TEST MIGRATION import { test, expect, vi } from 'vitest' -import { actions } from '@/store/index' +import { actions } from '@/stores/main' test('incrementAsync should work as expected', () => { vi.useFakeTimers() - const context = { commit: vi.fn() } - actions.incrementAsync(context) + const state = { increment: vi.fn() } + actions.incrementAsync.call(state) - expect(context.commit).not.toHaveBeenCalled() + expect(state.increment).not.toHaveBeenCalled(); vi.advanceTimersByTime(1000) - expect(context.commit).toHaveBeenCalledWith('increment') + expect(state.increment).toHaveBeenCalled(); vi.useRealTimers() }) 33
  • 34. COMPONENT MIGRATION (VUE 3) import { computed } from 'vue' -import { useStore } from 'vuex' +import { useStore } from '@/stores/main' const store = useStore() -const count = computed(() => store.state.count) -const isEven = computed(() => store.getters.isEven) -const isOdd = computed(() => store.getters.isOdd) +const count = computed(() => store.count) +const isEven = computed(() => store.isEven) +const isOdd = computed(() => store.isOdd) -const increment = () => store.commit('increment') -const incrementAsync = () => store.dispatch('incrementAsync') +const { increment, incrementAsync } = store 34
  • 35. COMPONENT MIGRATION (VUE 3) -import { computed } from 'vue' +import { storeToRefs } from 'pinia' -import { useStore } from 'vuex' +import { useStore } from '@/stores/main' const store = useStore() -const count = computed(() => store.state.count) -const isEven = computed(() => store.getters.isEven) -const isOdd = computed(() => store.getters.isOdd) +const { count, isEven, isOdd } = storeToRefs(store) -const increment = () => store.commit('increment') -const incrementAsync = () => store.dispatch('incrementAsync') +const { increment, incrementAsync } = store 35
  • 36. COMPONENT MIGRATION (VUE 2) -import { mapState, mapGetters, mapMutations, mapActions } fro +import { mapState, mapActions } from 'pinia' +import { useStore } from '@/stores/main' computed: { - ...mapState(['count']), - ...mapGetters(['isEven', 'isOdd']), + ...mapState(useStore, ['count', 'isEven', 'isOdd']), }, methods: { - ...mapMutations(['increment']), - ...mapActions(['incrementAsync']), + ...mapActions(useStore, ['increment', 'incrementAsync']), }, 36
  • 37. COMPONENT MIGRATION (VUE 2) -import { createNamespacedHelpers } from 'vuex' -import { GET_TODO_LIST } from '@/store/modules/todo-types' -const { mapState, mapActions } = createNamespacedHelpers('tod +import { mapState, mapActions } from 'pinia' +import { useTodoStore } from '@/stores/todo' computed: { - ...mapState(['todoList']), + ...mapState(useTodoStore, ['todoList']), }, mounted() { - this[GET_TODO_LIST]() + this.getTodoList() }, methods: { - ...mapActions([GET_TODO_LIST]), + ...mapActions(useTodoStore, ['getTodoList']), }, 37
  • 38. COMPONENT TESTING @pinia/testing npm install -D @pinia/testing import { createTestingPinia } from '@pinia/testing' createTestingPinia({ createSpy: vi.fn, initialState: { main: { count: 0 } }, }) 38
  • 39. createTestingPinia automatically mocks all actions unit test store and components separately allows you to overwrite getter values in tests (not working with Vue 2 and Jest) 39
  • 40. COMPONENT TESTING (VUE 3 + VITEST) import { createTestingPinia } from '@pinia/testing' import { shallowMount } from '@vue/test-utils'; import { useStore } from '@/stores/main' let pinia, store beforeEach(() => { pinia = createTestingPinia({ createSpy: vi.fn, initialState: { main: { count: 0 } }, }) // init store only after creating a testing pinia store = useStore() }) // tests const wrapper = shallowMount(Component, { global: { plugins: [pinia] } }) 40
  • 41. COMPONENT TESTING (VUE 2 + JEST) import { PiniaVuePlugin } from 'pinia' import { createTestingPinia } from '@pinia/testing' import { shallowMount, createLocalVue } from '@vue/test-utils' import { useStore } from '@/stores/main' const localVue = createLocalVue() localVue.use(PiniaVuePlugin) let pinia, store beforeEach(() => { pinia = createTestingPinia({ initialState: { main: { count: 0 } }, }) // init store only after creating a testing pinia store = useStore() }) // tests const wrapper = shallowMount(Component, { localVue, pinia }) 41
  • 42. COMPONENT TESTING (VUE 2 + JEST) Getters are not writable, you need to set the correct state in order to make them work as expected. store.count = 1 // or store.$patch({ count: 1, // other properties }) 42
  • 43. COMPONENT TEST MIGRATION (VUE 3 + VITEST) +import { createTestingPinia } from '@pinia/testing' import { describe, test, beforeEach, expect, vi } from 'vites -import { createStore } from 'vuex' import { shallowMount } from '@vue/test-utils'; import Counter from '@/components/Counter.vue' +import { useStore } from '@/stores/main' 43
  • 44. COMPONENT TEST MIGRATION (VUE 3 + VITEST) -let store +let pinia, store -let mutations = { increment: vi.fn() } -let actions = { incrementAsync: vi.fn() } beforeEach(() => { - store = createStore({ - state: () => ({ count: 0 }), - getters: { isEven: () => true, isOdd: () => false }, - mutations, - actions, - }) + pinia = createTestingPinia({ + createSpy: vi.fn, + initialState: { main: { count: 0 } }, + }) + store = useStore() }) 44
  • 45. COMPONENT TEST MIGRATION (VUE 3 + VITEST) test('should respect the snapshot', () => { const wrapper = shallowMount(Counter, { - global: { plugins: [store] } + global: { plugins: [pinia] } }) expect(wrapper.element).toMatchSnapshot() wrapper.findAll('button')[0].trigger('click') - expect(mutations.increment).toHaveBeenCalled() + expect(store.increment).toHaveBeenCalled() wrapper.findAll('button')[1].trigger('click') - expect(actions.incrementAsync).toHaveBeenCalled() + expect(store.incrementAsync).toHaveBeenCalled() }) 45
  • 46. COMPONENT TEST MIGRATION (VUE 2 + JEST) -import Vuex from 'vuex' +import { PiniaVuePlugin } from 'pinia' +import { createTestingPinia } from '@pinia/testing' import { shallowMount, createLocalVue } from '@vue/test-utils import Counter from '@/components/Counter.vue' +import { useStore } from '@/stores/main' const localVue = createLocalVue() -localVue.use(Vuex) +localVue.use(PiniaVuePlugin) 46
  • 47. COMPONENT TEST MIGRATION (VUE 2 + JEST) -let store +let pinia, store -let mutations = { increment: jest.fn() } -let actions = { incrementAsync: jest.fn() } beforeEach(() => { - store = createStore({ - state: { count: 0 }, - getters: { isEven: () => true, isOdd: () => false }, - mutations, - actions, - }) + pinia = createTestingPinia({ + initialState: { main: { count: 0 } }, + }) + store = useStore() }) 47
  • 48. COMPONENT TEST MIGRATION (VUE 2 + JEST) test('should respect the snapshot', () => { const wrapper = shallowMount(Counter, { - localVue, store, + localVue, pinia, }) expect(wrapper.element).toMatchSnapshot() wrapper.findAll('button')[0].trigger('click') - expect(mutations.increment).toHaveBeenCalled() + expect(store.increment).toHaveBeenCalled() wrapper.findAll('button')[1].trigger('click') - expect(actions.incrementAsync).toHaveBeenCalled() + expect(store.incrementAsync).toHaveBeenCalled() }) 48
  • 49. EVRYTHING SHOULD BE FINE, RIGHT? 49
  • 50. MIGRATION PROBLEMS NOTES Direct $store usage Use the correct store $store.state.propertyName $store.state.moduleName.propertyName $store.getters.getterName $store.getters['moduleName/getterName'] const myStore = useMyStore() myStore.propertyName myStore.getterName 50
  • 51. MIGRATION PROBLEMS NOTES Direct $store commit/dispatch Use store actions $store.commit.mutationName() $store.commit.moduleName.mutationName() $store.dispatch.actionName() $store.dispatch['moduleName/actionName']() const myStore = useMyStore() myStore.actionName() 51
  • 52. MIGRATION PROBLEMS NOTES What about a global useStore()? If used outside of <script setup>, will give you this error import { useStore } from '@/stores/main' const store = useStore() // other stuff getActivePinia was called with no active Pinia. Did you forget to install pinia? 52
  • 53. MIGRATION PROBLEMS NOTES Can Vuex and Pinia coexist? Yes, but... make sure to migrate entire modules, not entire components. 53
  • 54. MIGRATION PROBLEMS NOTES How to test a real store? import { setActivePinia, createPinia } from 'pinia' beforeEach(() => { setActivePinia(createPinia()) }) test('should work as expected', () => { const store = useStore() // ... }) 54
  • 55. MIGRATION PROBLEMS NOTES What about store persistence? myStore.$subscribe((mutation, state) => { localStorage.setItem('myStore', JSON.stringify(state)) }) // restore with myStore.$state = { /* ... */ } watch( pinia.state, (state) => { localStorage.setItem('piniaState', JSON.stringify(state)) }, { deep: true } ) // restore with pinia.state.value = { /* ... */ } https://pinia.vuejs.org/core-concepts/state.html#subscribing-to-the-state 55
  • 56. FINAL TASKS 1. remove Vuex store from main.js -import store from './store' // Vue 3 -app.use(store) // Vue 2 new Vue({ router, - store, pinia, render: h => h(App), }).$mount('#app') 56
  • 57. FINAL TASKS 2. delete Vuex store and tests 3. uninstall Vuex dependencies rm -rf src/store rm -rf tests/unit/store npm uninstall vuex @vue/cli-plugin-vuex 57