Migration from Vuex in Shopware to Pinia
With the release of Shopware 6.7, we will replace Vuex with Pinia as the state management library for the administration.
Why Pinia?
Migrating to Pinia simplifies state management with an intuitive API, eliminates the need for mutations, provides better TypeScript support, and enables seamless integration with Vue 3 Composition API. It’s lightweight, modular, and offers modern features like devtools support, making it a more efficient alternative to Vuex.
Migration Guide
To migrate a Vuex store to Pinia, you need to update the store definition and how you access it in components.
- First, register it with
Shopware.Store.registerand define the store withstate,getters, andactionsproperties:
Before (Vuex):
export default {
namespaced: true,
state: {
// Initial state
...
},
mutations: {
...
},
getters: {
...
},
actions: {
...
},
}After (Pinia):
const store = Shopware.Store.register('<storeName>', {
state: () => ({
// Initial state
...
}),
getters: {
...
},
actions: {
...
},
});
export default store;- You can also register the store with an
idproperty in the definition object, for example:
const store = Shopware.Store.register({
id: '<storeName>',
state: () => ({
// Initial state
}),
getters: {
// ...
},
actions: {
// ...
},
});- If you register a store that already exists, it will be overwritten. You can also unregister a store:
Shopware.Store.unregister('<storeName>');- To register a store from a component or index file, simply import the store file.
Before (Vuex):
import productsStore from './state/products.state';
Shopware.State.registerModule('product', productsStore);After (Pinia):
import './state/products.state';Key Changes
State
In Pinia, state must be a function that returns the initial state rather than a static object.
state: () => ({
productName: '',
})Mutations
Vuex mutations are no longer needed in Pinia, since you can modify the state directly in actions or compute it dynamically.
actions: {
updateProductName(newName) {
this.productName = newName; // Directly update state
},
},Getters
- There cannot be getters with the same name as a property in the state, as both are exposed at the same level in the store.
- Getters should be used to compute and return information based on state, without modifying it.
TypeScript
We recommend migrating JavaScript stores to TypeScript for stricter typing, better autocompletion, and fewer errors during development.
const store = Shopware.Store.register({
id: 'myStore',
...
});
export type StoreType = ReturnType<typeof store>;Then, you can use this type to extend PiniaRootState:
import type { StoreType } from './store/myStore';
declare global {
interface PiniaRootState {
myStore: StoreType;
}
}Composables as a Store
With Pinia, you can use reactive properties in a store and define them as composable. Keep in mind that Pinia will track only variables and functions returned from the store in devtools.
const store = Shopware.Store.register('<storeName>', function() {
const count = ref(0);
const doubled = computed(() => count.value * 2);
function increment() {
count.value++;
}
function decrement() {
count.value--;
}
return { count, doubled, increment, decrement };
});You can also use a composable function defined outside the store. This allows you to encapsulate and reuse logic across different stores or components, promoting better code organization and modularity:
// composables/myComposable.ts
export function useMyComposable() {
const count = ref(0);
const doubled = computed(() => count.value * 2);
function increment() {
count.value++;
}
function decrement() {
count.value--;
}
return { count, doubled, increment, decrement };
}// store/myStore.ts
import { useMyComposable } from '../composables/myComposable';
const store = Shopware.Store.register('myStore', useMyComposable);Accessing the Store
To access the store in Vuex, you would typically do:
Shopware.State.get('<storeName>');When migrating to Pinia, it changes to:
Shopware.Store.get('<storeName>');Testing
To test your store, just import it so it's registered. You can use $reset() to reset the store before each test:
import './store/my.store';
describe('my store', () => {
const store = Shopware.Store.get('myStore');
beforeEach(() => {
store.$reset();
});
it('has initial state', () => {
expect(store.count).toBe(0);
});
});When testing components that use Pinia stores, register Pinia as a plugin and reset it before each test:
import { createPinia, setActivePinia } from 'pinia';
const pinia = createPinia();
describe('my component', () => {
beforeEach(() => {
setActivePinia(pinia);
});
it('is a component', async () => {
const wrapper = mount(await wrapTestComponent('myComponent', { sync: true }), {
global: {
plugins: [pinia],
stubs: {
// ...
},
},
});
expect(wrapper.exists()).toBe(true);
});
});