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

[Final] ReactJS presentation
[Final] ReactJS presentation[Final] ReactJS presentation
[Final] ReactJS presentation
洪 鹏发
 
Presentation Spring, Spring MVC
Presentation Spring, Spring MVCPresentation Spring, Spring MVC
Presentation Spring, Spring MVC
Nathaniel Richand
 

What's hot (20)

Introduction to three.js
Introduction to three.jsIntroduction to three.js
Introduction to three.js
 
Soap and Rest
Soap and RestSoap and Rest
Soap and Rest
 
Introduction to Node.js
Introduction to Node.jsIntroduction to Node.js
Introduction to Node.js
 
Writing Domain Specific Languages with JSON Schema
Writing Domain Specific Languages with JSON SchemaWriting Domain Specific Languages with JSON Schema
Writing Domain Specific Languages with JSON Schema
 
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?
 
Quora Pitch Deck
Quora Pitch DeckQuora Pitch Deck
Quora Pitch Deck
 
Building Distributed Systems With Riak and Riak Core
Building Distributed Systems With Riak and Riak CoreBuilding Distributed Systems With Riak and Riak Core
Building Distributed Systems With Riak and Riak Core
 
여기컨_스타트업 기획자의 월화수목금_이수지
여기컨_스타트업 기획자의 월화수목금_이수지여기컨_스타트업 기획자의 월화수목금_이수지
여기컨_스타트업 기획자의 월화수목금_이수지
 
introduction à MongoDB
introduction à MongoDBintroduction à MongoDB
introduction à MongoDB
 
[Final] ReactJS presentation
[Final] ReactJS presentation[Final] ReactJS presentation
[Final] ReactJS presentation
 
Spring Boot RestApi.pptx
Spring Boot RestApi.pptxSpring Boot RestApi.pptx
Spring Boot RestApi.pptx
 
토스 이직기 & 적응기 (99Con)
토스 이직기 & 적응기 (99Con)토스 이직기 & 적응기 (99Con)
토스 이직기 & 적응기 (99Con)
 
スクラムによる広告管理ツールの刷新と改善 #yjtc / YJTC21 B-4
スクラムによる広告管理ツールの刷新と改善 #yjtc / YJTC21 B-4スクラムによる広告管理ツールの刷新と改善 #yjtc / YJTC21 B-4
スクラムによる広告管理ツールの刷新と改善 #yjtc / YJTC21 B-4
 
Load Balancing with Apache
Load Balancing with ApacheLoad Balancing with Apache
Load Balancing with Apache
 
VueJS Best Practices
VueJS Best PracticesVueJS Best Practices
VueJS Best Practices
 
Tesla Investor Presentation - Model S
Tesla Investor Presentation - Model STesla Investor Presentation - Model S
Tesla Investor Presentation - Model S
 
Spring 3.1 and MVC Testing Support
Spring 3.1 and MVC Testing SupportSpring 3.1 and MVC Testing Support
Spring 3.1 and MVC Testing Support
 
Presentation Spring, Spring MVC
Presentation Spring, Spring MVCPresentation Spring, Spring MVC
Presentation Spring, Spring MVC
 
Formation Spring Avancé gratuite par Ippon 2014
Formation Spring Avancé gratuite par Ippon 2014Formation Spring Avancé gratuite par Ippon 2014
Formation Spring Avancé gratuite par Ippon 2014
 
Workshop 21: React Router
Workshop 21: React RouterWorkshop 21: React Router
Workshop 21: React Router
 

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

20150516 modern web_conf_tw
20150516 modern web_conf_tw20150516 modern web_conf_tw
20150516 modern web_conf_tw
Tse-Ching Ho
 

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

%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
masabamasaba
 
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
VictoriaMetrics
 

Recently uploaded (20)

Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
Direct Style Effect Systems -The Print[A] Example- A Comprehension AidDirect Style Effect Systems -The Print[A] Example- A Comprehension Aid
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
 
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
 
tonesoftg
tonesoftgtonesoftg
tonesoftg
 
WSO2CON 2024 - API Management Usage at La Poste and Its Impact on Business an...
WSO2CON 2024 - API Management Usage at La Poste and Its Impact on Business an...WSO2CON 2024 - API Management Usage at La Poste and Its Impact on Business an...
WSO2CON 2024 - API Management Usage at La Poste and Its Impact on Business an...
 
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
 
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...
 
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
 
%in Benoni+277-882-255-28 abortion pills for sale in Benoni
%in Benoni+277-882-255-28 abortion pills for sale in Benoni%in Benoni+277-882-255-28 abortion pills for sale in Benoni
%in Benoni+277-882-255-28 abortion pills for sale in Benoni
 
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital TransformationWSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
 
Architecture decision records - How not to get lost in the past
Architecture decision records - How not to get lost in the pastArchitecture decision records - How not to get lost in the past
Architecture decision records - How not to get lost in the past
 
Announcing Codolex 2.0 from GDK Software
Announcing Codolex 2.0 from GDK SoftwareAnnouncing Codolex 2.0 from GDK Software
Announcing Codolex 2.0 from GDK Software
 
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
 
WSO2CON 2024 - Does Open Source Still Matter?
WSO2CON 2024 - Does Open Source Still Matter?WSO2CON 2024 - Does Open Source Still Matter?
WSO2CON 2024 - Does Open Source Still Matter?
 
%in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park %in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park
 
AI & Machine Learning Presentation Template
AI & Machine Learning Presentation TemplateAI & Machine Learning Presentation Template
AI & Machine Learning Presentation Template
 
WSO2Con2024 - From Code To Cloud: Fast Track Your Cloud Native Journey with C...
WSO2Con2024 - From Code To Cloud: Fast Track Your Cloud Native Journey with C...WSO2Con2024 - From Code To Cloud: Fast Track Your Cloud Native Journey with C...
WSO2Con2024 - From Code To Cloud: Fast Track Your Cloud Native Journey with C...
 
WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...
WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...
WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...
 
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
 
Artyushina_Guest lecture_YorkU CS May 2024.pptx
Artyushina_Guest lecture_YorkU CS May 2024.pptxArtyushina_Guest lecture_YorkU CS May 2024.pptx
Artyushina_Guest lecture_YorkU CS May 2024.pptx
 
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
 

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