SlideShare a Scribd company logo
1 of 58
Download to read offline
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

What's hot

Spring Boot & Containers - Do's & Don'ts
Spring Boot & Containers - Do's & Don'tsSpring Boot & Containers - Do's & Don'ts
Spring Boot & Containers - Do's & Don'tsJulien Wittouck
 
Spring Framework - Core
Spring Framework - CoreSpring Framework - Core
Spring Framework - CoreDzmitry Naskou
 
SpringBoot with MyBatis, Flyway, QueryDSL
SpringBoot with MyBatis, Flyway, QueryDSLSpringBoot with MyBatis, Flyway, QueryDSL
SpringBoot with MyBatis, Flyway, QueryDSLSunghyouk Bae
 
오픈소스로 만드는 DB 모니터링 시스템 (w/graphite+grafana)
오픈소스로 만드는 DB 모니터링 시스템 (w/graphite+grafana)오픈소스로 만드는 DB 모니터링 시스템 (w/graphite+grafana)
오픈소스로 만드는 DB 모니터링 시스템 (w/graphite+grafana)I Goo Lee
 
Sling Models Using Sightly and JSP by Deepak Khetawat
Sling Models Using Sightly and JSP by Deepak KhetawatSling Models Using Sightly and JSP by Deepak Khetawat
Sling Models Using Sightly and JSP by Deepak KhetawatAEM HUB
 
IT Sapiens-SAP ITS Mobile Solution With Responsive Design for Multi Device Ad...
IT Sapiens-SAP ITS Mobile Solution With Responsive Design for Multi Device Ad...IT Sapiens-SAP ITS Mobile Solution With Responsive Design for Multi Device Ad...
IT Sapiens-SAP ITS Mobile Solution With Responsive Design for Multi Device Ad...Vijay Pisipaty
 
Tagless Final Encoding - Algebras and Interpreters and also Programs
Tagless Final Encoding - Algebras and Interpreters and also ProgramsTagless Final Encoding - Algebras and Interpreters and also Programs
Tagless Final Encoding - Algebras and Interpreters and also ProgramsPhilip Schwarz
 
점진적인 레거시 웹 애플리케이션 개선 과정
점진적인 레거시 웹 애플리케이션 개선 과정점진적인 레거시 웹 애플리케이션 개선 과정
점진적인 레거시 웹 애플리케이션 개선 과정Arawn Park
 
Modern Java web applications with Spring Boot and Thymeleaf
Modern Java web applications with Spring Boot and ThymeleafModern Java web applications with Spring Boot and Thymeleaf
Modern Java web applications with Spring Boot and ThymeleafLAY Leangsros
 

What's hot (17)

Javascript Best Practices
Javascript Best PracticesJavascript Best Practices
Javascript Best Practices
 
Spring Boot & Containers - Do's & Don'ts
Spring Boot & Containers - Do's & Don'tsSpring Boot & Containers - Do's & Don'ts
Spring Boot & Containers - Do's & Don'ts
 
Spring Framework - Core
Spring Framework - CoreSpring Framework - Core
Spring Framework - Core
 
Spring User Guide
Spring User GuideSpring User Guide
Spring User Guide
 
Spool process
Spool processSpool process
Spool process
 
WEBINAR PPT.pptx
WEBINAR PPT.pptxWEBINAR PPT.pptx
WEBINAR PPT.pptx
 
Ad java prac sol set
Ad java prac sol setAd java prac sol set
Ad java prac sol set
 
Antivirus works
Antivirus worksAntivirus works
Antivirus works
 
SpringBoot with MyBatis, Flyway, QueryDSL
SpringBoot with MyBatis, Flyway, QueryDSLSpringBoot with MyBatis, Flyway, QueryDSL
SpringBoot with MyBatis, Flyway, QueryDSL
 
오픈소스로 만드는 DB 모니터링 시스템 (w/graphite+grafana)
오픈소스로 만드는 DB 모니터링 시스템 (w/graphite+grafana)오픈소스로 만드는 DB 모니터링 시스템 (w/graphite+grafana)
오픈소스로 만드는 DB 모니터링 시스템 (w/graphite+grafana)
 
Sling Models Using Sightly and JSP by Deepak Khetawat
Sling Models Using Sightly and JSP by Deepak KhetawatSling Models Using Sightly and JSP by Deepak Khetawat
Sling Models Using Sightly and JSP by Deepak Khetawat
 
Sequelize
SequelizeSequelize
Sequelize
 
IT Sapiens-SAP ITS Mobile Solution With Responsive Design for Multi Device Ad...
IT Sapiens-SAP ITS Mobile Solution With Responsive Design for Multi Device Ad...IT Sapiens-SAP ITS Mobile Solution With Responsive Design for Multi Device Ad...
IT Sapiens-SAP ITS Mobile Solution With Responsive Design for Multi Device Ad...
 
Tagless Final Encoding - Algebras and Interpreters and also Programs
Tagless Final Encoding - Algebras and Interpreters and also ProgramsTagless Final Encoding - Algebras and Interpreters and also Programs
Tagless Final Encoding - Algebras and Interpreters and also Programs
 
점진적인 레거시 웹 애플리케이션 개선 과정
점진적인 레거시 웹 애플리케이션 개선 과정점진적인 레거시 웹 애플리케이션 개선 과정
점진적인 레거시 웹 애플리케이션 개선 과정
 
Modern Java web applications with Spring Boot and Thymeleaf
Modern Java web applications with Spring Boot and ThymeleafModern Java web applications with Spring Boot and Thymeleaf
Modern Java web applications with Spring Boot and Thymeleaf
 
Monads do not Compose
Monads do not ComposeMonads do not Compose
Monads do not Compose
 

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

Introducing Vuex in your project
Introducing Vuex in your projectIntroducing Vuex in your project
Introducing Vuex in your projectDenny Biasiolli
 
State manager in Vue.js, from zero to Vuex
State manager in Vue.js, from zero to VuexState manager in Vue.js, from zero to Vuex
State manager in Vue.js, from zero to VuexCommit University
 
Is it time to migrate to Vue 3?
Is it time to migrate to Vue 3?Is it time to migrate to Vue 3?
Is it time to migrate to Vue 3?Denny Biasiolli
 
Battle of React State Managers in frontend applications
Battle of React State Managers in frontend applicationsBattle of React State Managers in frontend applications
Battle of React State Managers in frontend applicationsEvangelia Mitsopoulou
 
How to Build SPA with Vue Router 2.0
How to Build SPA with Vue Router 2.0How to Build SPA with Vue Router 2.0
How to Build SPA with Vue Router 2.0Takuya Tejima
 
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 typescriptKaty Slemon
 
Yves & Zed @ Developer Conference 2013
Yves & Zed @ Developer Conference 2013Yves & Zed @ Developer Conference 2013
Yves & Zed @ Developer Conference 2013FabianWesnerBerlin
 
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 2018Codemotion
 
Building and deploying React applications
Building and deploying React applicationsBuilding and deploying React applications
Building and deploying React applicationsAstrails
 
React + Redux. Best practices
React + Redux.  Best practicesReact + Redux.  Best practices
React + Redux. Best practicesClickky
 
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éutilisablesRiad Benguella
 
20150516 modern web_conf_tw
20150516 modern web_conf_tw20150516 modern web_conf_tw
20150516 modern web_conf_twTse-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 2017Elyse Kolker Gordon
 

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

Introducing Vuex in your project
Introducing Vuex in your projectIntroducing Vuex in your project
Introducing Vuex in your project
 
State manager in Vue.js, from zero to Vuex
State manager in Vue.js, from zero to VuexState manager in Vue.js, from zero to Vuex
State manager in Vue.js, from zero to Vuex
 
Is it time to migrate to Vue 3?
Is it time to migrate to Vue 3?Is it time to migrate to Vue 3?
Is it time to migrate to Vue 3?
 
Battle of React State Managers in frontend applications
Battle of React State Managers in frontend applicationsBattle of React State Managers in frontend applications
Battle of React State Managers in frontend applications
 
Side effects-con-redux
Side effects-con-reduxSide effects-con-redux
Side effects-con-redux
 
How to Build SPA with Vue Router 2.0
How to Build SPA with Vue Router 2.0How to Build SPA with Vue Router 2.0
How to Build SPA with Vue Router 2.0
 
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
 

Recently uploaded

WSO2CON 2024 - Designing Event-Driven Enterprises: Stories of Transformation
WSO2CON 2024 - Designing Event-Driven Enterprises: Stories of TransformationWSO2CON 2024 - Designing Event-Driven Enterprises: Stories of Transformation
WSO2CON 2024 - Designing Event-Driven Enterprises: Stories of TransformationWSO2
 
The mythical technical debt. (Brooke, please, forgive me)
The mythical technical debt. (Brooke, please, forgive me)The mythical technical debt. (Brooke, please, forgive me)
The mythical technical debt. (Brooke, please, forgive me)Roberto Bettazzoni
 
BusinessGPT - Security and Governance for Generative AI
BusinessGPT  - Security and Governance for Generative AIBusinessGPT  - Security and Governance for Generative AI
BusinessGPT - Security and Governance for Generative AIAGATSoftware
 
WSO2Con2024 - Unleashing the Financial Potential of 13 Million People
WSO2Con2024 - Unleashing the Financial Potential of 13 Million PeopleWSO2Con2024 - Unleashing the Financial Potential of 13 Million People
WSO2Con2024 - Unleashing the Financial Potential of 13 Million PeopleWSO2
 
WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...
WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...
WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...WSO2
 
Driving Innovation: Scania's API Revolution with WSO2
Driving Innovation: Scania's API Revolution with WSO2Driving Innovation: Scania's API Revolution with WSO2
Driving Innovation: Scania's API Revolution with WSO2WSO2
 
WSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
WSO2Con2024 - Enabling Transactional System's Exponential Growth With SimplicityWSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
WSO2Con2024 - Enabling Transactional System's Exponential Growth With SimplicityWSO2
 
WSO2CON2024 - Why Should You Consider Ballerina for Your Next Integration
WSO2CON2024 - Why Should You Consider Ballerina for Your Next IntegrationWSO2CON2024 - Why Should You Consider Ballerina for Your Next Integration
WSO2CON2024 - Why Should You Consider Ballerina for Your Next IntegrationWSO2
 
From Theory to Practice: Utilizing SpiraPlan's REST API
From Theory to Practice: Utilizing SpiraPlan's REST APIFrom Theory to Practice: Utilizing SpiraPlan's REST API
From Theory to Practice: Utilizing SpiraPlan's REST APIInflectra
 
WSO2CON 2024 - Unlocking the Identity: Embracing CIAM 2.0 for a Competitive A...
WSO2CON 2024 - Unlocking the Identity: Embracing CIAM 2.0 for a Competitive A...WSO2CON 2024 - Unlocking the Identity: Embracing CIAM 2.0 for a Competitive A...
WSO2CON 2024 - Unlocking the Identity: Embracing CIAM 2.0 for a Competitive A...WSO2
 
[GeeCON2024] How I learned to stop worrying and love the dark silicon apocalypse
[GeeCON2024] How I learned to stop worrying and love the dark silicon apocalypse[GeeCON2024] How I learned to stop worrying and love the dark silicon apocalypse
[GeeCON2024] How I learned to stop worrying and love the dark silicon apocalypseTomasz Kowalczewski
 
What Goes Wrong with Language Definitions and How to Improve the Situation
What Goes Wrong with Language Definitions and How to Improve the SituationWhat Goes Wrong with Language Definitions and How to Improve the Situation
What Goes Wrong with Language Definitions and How to Improve the SituationJuha-Pekka Tolvanen
 
WSO2CON 2024 Slides - Unlocking Value with AI
WSO2CON 2024 Slides - Unlocking Value with AIWSO2CON 2024 Slides - Unlocking Value with AI
WSO2CON 2024 Slides - Unlocking Value with AIWSO2
 
WSO2CON 2024 - Not Just Microservices: Rightsize Your Services!
WSO2CON 2024 - Not Just Microservices: Rightsize Your Services!WSO2CON 2024 - Not Just Microservices: Rightsize Your Services!
WSO2CON 2024 - Not Just Microservices: Rightsize Your Services!WSO2
 
Novo Nordisk: When Knowledge Graphs meet LLMs
Novo Nordisk: When Knowledge Graphs meet LLMsNovo Nordisk: When Knowledge Graphs meet LLMs
Novo Nordisk: When Knowledge Graphs meet LLMsNeo4j
 
WSO2Con2024 - Hello Choreo Presentation - Kanchana
WSO2Con2024 - Hello Choreo Presentation - KanchanaWSO2Con2024 - Hello Choreo Presentation - Kanchana
WSO2Con2024 - Hello Choreo Presentation - KanchanaWSO2
 
WSO2CON 2024 - How CSI Piemonte Is Apifying the Public Administration
WSO2CON 2024 - How CSI Piemonte Is Apifying the Public AdministrationWSO2CON 2024 - How CSI Piemonte Is Apifying the Public Administration
WSO2CON 2024 - How CSI Piemonte Is Apifying the Public AdministrationWSO2
 
WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...
WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...
WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...WSO2
 
Evolving Data Governance for the Real-time Streaming and AI Era
Evolving Data Governance for the Real-time Streaming and AI EraEvolving Data Governance for the Real-time Streaming and AI Era
Evolving Data Governance for the Real-time Streaming and AI Eraconfluent
 
Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...
Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...
Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...Lisi Hocke
 

Recently uploaded (20)

WSO2CON 2024 - Designing Event-Driven Enterprises: Stories of Transformation
WSO2CON 2024 - Designing Event-Driven Enterprises: Stories of TransformationWSO2CON 2024 - Designing Event-Driven Enterprises: Stories of Transformation
WSO2CON 2024 - Designing Event-Driven Enterprises: Stories of Transformation
 
The mythical technical debt. (Brooke, please, forgive me)
The mythical technical debt. (Brooke, please, forgive me)The mythical technical debt. (Brooke, please, forgive me)
The mythical technical debt. (Brooke, please, forgive me)
 
BusinessGPT - Security and Governance for Generative AI
BusinessGPT  - Security and Governance for Generative AIBusinessGPT  - Security and Governance for Generative AI
BusinessGPT - Security and Governance for Generative AI
 
WSO2Con2024 - Unleashing the Financial Potential of 13 Million People
WSO2Con2024 - Unleashing the Financial Potential of 13 Million PeopleWSO2Con2024 - Unleashing the Financial Potential of 13 Million People
WSO2Con2024 - Unleashing the Financial Potential of 13 Million People
 
WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...
WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...
WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...
 
Driving Innovation: Scania's API Revolution with WSO2
Driving Innovation: Scania's API Revolution with WSO2Driving Innovation: Scania's API Revolution with WSO2
Driving Innovation: Scania's API Revolution with WSO2
 
WSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
WSO2Con2024 - Enabling Transactional System's Exponential Growth With SimplicityWSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
WSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
 
WSO2CON2024 - Why Should You Consider Ballerina for Your Next Integration
WSO2CON2024 - Why Should You Consider Ballerina for Your Next IntegrationWSO2CON2024 - Why Should You Consider Ballerina for Your Next Integration
WSO2CON2024 - Why Should You Consider Ballerina for Your Next Integration
 
From Theory to Practice: Utilizing SpiraPlan's REST API
From Theory to Practice: Utilizing SpiraPlan's REST APIFrom Theory to Practice: Utilizing SpiraPlan's REST API
From Theory to Practice: Utilizing SpiraPlan's REST API
 
WSO2CON 2024 - Unlocking the Identity: Embracing CIAM 2.0 for a Competitive A...
WSO2CON 2024 - Unlocking the Identity: Embracing CIAM 2.0 for a Competitive A...WSO2CON 2024 - Unlocking the Identity: Embracing CIAM 2.0 for a Competitive A...
WSO2CON 2024 - Unlocking the Identity: Embracing CIAM 2.0 for a Competitive A...
 
[GeeCON2024] How I learned to stop worrying and love the dark silicon apocalypse
[GeeCON2024] How I learned to stop worrying and love the dark silicon apocalypse[GeeCON2024] How I learned to stop worrying and love the dark silicon apocalypse
[GeeCON2024] How I learned to stop worrying and love the dark silicon apocalypse
 
What Goes Wrong with Language Definitions and How to Improve the Situation
What Goes Wrong with Language Definitions and How to Improve the SituationWhat Goes Wrong with Language Definitions and How to Improve the Situation
What Goes Wrong with Language Definitions and How to Improve the Situation
 
WSO2CON 2024 Slides - Unlocking Value with AI
WSO2CON 2024 Slides - Unlocking Value with AIWSO2CON 2024 Slides - Unlocking Value with AI
WSO2CON 2024 Slides - Unlocking Value with AI
 
WSO2CON 2024 - Not Just Microservices: Rightsize Your Services!
WSO2CON 2024 - Not Just Microservices: Rightsize Your Services!WSO2CON 2024 - Not Just Microservices: Rightsize Your Services!
WSO2CON 2024 - Not Just Microservices: Rightsize Your Services!
 
Novo Nordisk: When Knowledge Graphs meet LLMs
Novo Nordisk: When Knowledge Graphs meet LLMsNovo Nordisk: When Knowledge Graphs meet LLMs
Novo Nordisk: When Knowledge Graphs meet LLMs
 
WSO2Con2024 - Hello Choreo Presentation - Kanchana
WSO2Con2024 - Hello Choreo Presentation - KanchanaWSO2Con2024 - Hello Choreo Presentation - Kanchana
WSO2Con2024 - Hello Choreo Presentation - Kanchana
 
WSO2CON 2024 - How CSI Piemonte Is Apifying the Public Administration
WSO2CON 2024 - How CSI Piemonte Is Apifying the Public AdministrationWSO2CON 2024 - How CSI Piemonte Is Apifying the Public Administration
WSO2CON 2024 - How CSI Piemonte Is Apifying the Public Administration
 
WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...
WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...
WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...
 
Evolving Data Governance for the Real-time Streaming and AI Era
Evolving Data Governance for the Real-time Streaming and AI EraEvolving Data Governance for the Real-time Streaming and AI Era
Evolving Data Governance for the Real-time Streaming and AI Era
 
Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...
Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...
Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...
 

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