Access default state in Pinia - vuex

I am trying to migrate from Vuex to Pinia and I couldn't figure out what defaultStateFactory() equivalent is in Pinia.
Here is an mutation from Vuex. I want to convert this to Pinia.
setOperatedAddress(state, payload) {
if (payload) {
Object.assign(state.operatedAddress, payload)
} else {
Object.assign(state.operatedAddress, defaultStateFactory().operatedAddress)
}
}
If no address provided I want operated address to fall back to initial empty state.

You can achieve this simply by defining your state function externally, and use it both in the store definition and inside your actions. Something like this:
const defaultStateFactory = () => {
return {
operatedAddress: 'foo'
}
}
const myStore = defineStore('myStore', {
state: defaultStateFactory,
actions: {
setOperatedAddress(payload) {
this.operatedAddress = payload ?? defaultStateFactory().operatedAddress
}
},
})

Related

Navigation component not getting re rendered with stage change in Vue3

When a user updates their username in the EditAccount component, the username is updated in the EditAccount component and in vuex store but not in the Navigation component even though stage change is updated to the new user name.
The problem is that the user is seing thier old user name in Navigation component and a updated user name in the EditAccount component and they don't match.
How can I Re render the Navigation component with the new user name?
Below is the the code for user the data in the Navigation component.
Store vuex: index.js
const store = createStore({
// strict: true,
state: {
user: null,
authIsReady: false,
//
// current category
playlistCategory: null,
},
//
getters: {
getUser(state) {
return state.user;
},
},
mutations: {
//
// update playlist category
updatePlaylistCategory(state, payload) {
state.playlistCategory = payload;
},
//
//
setUser(state, payload) {
state.user = payload;
},
//
setAuthIsReady(state, payload) {
state.authIsReady = payload;
},
//
},
actions: {
async editUser(context, payload) {
const { displayNewName, displayNewEmail } = payload;
await updateUserDetails(displayNewName, displayNewEmail);
// get current user
const responseUser = await user;
// set user state
context.commit('setUser', responseUser);
},
},
NavBar.vue
// vue3 and composition api
setup() {
// store
const store = useStore()
//
const { error, logout, isPending } = useLogout()
const router = useRouter()
//
// getters
const user = computed(() => {
return store.getters.getUser.displayName
})
Try adding set and get property:
const user = computed({
get: store.state.user,
set: (val) => store.state.user = val
});
Try using a getter instead acessing the value directly in the state
Getter for user:
export function getUser(state){
return state.getUser
}
and in the component import the getter like this:
<script>
import {mapGetters} from 'vuex'
export default {
computed: {
...mapGetters('*theStoreName*',['getUser'])
},
watch: {
getUser: function(){
//Should be possible to see when the getUser changes here
console.log(this.getUser)
}
}
}
</script>
Note: You have theStoreName for the store name you're using
Maybe the problem is that the store name is missing, or when you did store.state.user you're acessing the store? If it is it, then you should try to inform the variable you're trying to access, like If it is, like store.state.user.name, with the getter it would be: getUser.name

Updating getter value Vuex store when state changes

I'm trying to figure out how to properly update a getter value when some other variable from VueX changes/updates.
Currently I'm using this way in a component to update:
watch: {
dates () {
this.$set(this.linedata[0].chartOptions.xAxis,"categories",this.dates)
}
}
So my getter linedata should be updated with dates value whenever dates changes. dates is state variable from VueX store.
The thing is with this method the value won't be properly updated when I changed route/go to different components. So I think it's better to do this kind of thing using the VueX store.
dates is updated with an API call, so I use an action to update it.
So the question is how can I do such an update from the VueX store?
EDIT:
I tried moving this to VueX:
async loadData({ commit }) {
let response = await Api().get("/cpu");
commit("SET_DATA", {
this.linedata[0].chartOptions.xAxis,"categories": response.data.dates1,
this.linedata[1].chartOptions.xAxis,"categories": response.data.dates2
});
}
SET_DATA(state, payload) {
state = Object.assign(state, payload);
}
But the above does not work, as I cannot set nested object in action this way...
Getters are generally for getting, not setting. They are like computed for Vuex, which return calculated data. They update automatically when reactive contents change. So it's probably best to rethink the design so that only state needs to be updated. Either way, Vuex should be updated only with actions/mutations
Given your example and the info from all your comments, using linedata as state, your action and mutation would look something like this:
actions: {
async loadData({ commit }) {
let response = await Api().get("/cpu");
commit('SET_DATA', response.data.dates);
}
}
mutations: {
SET_DATA(state, dates) {
Vue.set(state.linedata[0].chartOptions.xAxis, 'categories', dates[0]);
Vue.set(state.linedata[1].chartOptions.xAxis, 'categories', dates[1]);
}
}
Which you could call, in the component for example, like:
this.$store.dispatch('loadData');
Using Vue.set is necessary for change detection in this case and requires the following import:
import Vue from 'vue';
Theoretically, there should be a better way to design your backend API so that you can just set state.linedata = payload in the mutation, but this will work with what you have.
Here is a simple example of a Vuex store for an user.
export const state = () => ({
user: {}
})
export const mutations = {
set(state, user) {
state.user = user
},
unset(state) {
state.user = {}
},
patch(state, user) {
state.user = Object.assign({}, state.user, user)
}
}
export const actions = {
async set({ commit }) {
// TODO: Get user...
commit('set', user)
},
unset({ commit }) {
commit('unset')
},
patch({ commit }, user) {
commit('patch', user)
}
}
export const getters = {
get(state) {
return state.user
}
}
If you want to set the user data, you can call await this.$store.dispatch('user/set') in any Vue instance. For patching the data you could call this.$store.dispatch('user/patch', newUserData).
The getter is then reactively updated in any Vue instance where it is mapped. You should use the function mapGetters from Vuex in the computed properties. Here is an example.
...
computed: {
...mapGetters({
user: 'user/get'
})
}
...
The three dots ... before the function call is destructuring assignment, which will map all the properties that will the function return in an object to computed properties. Those will then be reactively updated whenever you call dispatch on the user store.
Take a look at Vuex documentation for a more in depth explanation.

Vuex - Cloned state into data object property, can't delete array item

I've cloned my state from Vuex to an array in my component, data(). My problem is when I'm trying to remove the first item in the array from my clone with shift()and also add it back with unshift() I get this error msg:
[vuex] Do not mutate vuex store state outside mutation handlers.
How can I delete something in my cloned state that's not effects the actually state itself?
Saving/cloning
beforeMount () {
this.traningArea = this.stateExercises
},
computed: {
...mapState({
userStore: state => state.userStore,
tsStore: state => state.trainingSchemeStore
}),
stateExercises () {
return this.tsStore.schemeExercises
}
}
Trying to run shift() on click and unshift() if user click again
this.traningArea[0].shift()
this.traningArea[0].unshift(obj)
And it's here I've got this error.
STATE
const state = {
trainings: []
}
const actions = {
getTrainingExercise: ({commit}, ids) => {
var payload = {
'trainings_id': ids
}
return Vue.http.post(getTrainingsById, payload,
{headers: getHeader()})
.then(response => {
if (response.status === 200) {
commit('SET_TERL', response.body.data)
}
})
},
const mutations = {
SET_TERL(state, trainings) {
state.trainings.push(trainings)
}
}
i hope that i don't misunderstand you, so i think that the solution would be like this :
const mutations = {
SET_TERL(state, trainings) {
state.trainings.push(trainings)
},
SHIFT(state, index) {
state.trainings[index].shift()
},
UNSHIFT(state, index,obj) {
state.trainings[index].unshift(obj)
}
}
and when you call the method :
this.tsStore.commit("SHIFT",0);
or
this.tsStore.commit("UNSHIFT",0,obj);

VueJS - Dynamic State Management multiple instances

I am creating an app and I have a component "Message" which uses a store to get data back from a JSON file (this will be eventually a database) and the component is as follows:
export default {
props: ['message'],
mounted: function() {
this.$store.dispatch("FETCHMESSAGE", this.message);
},
computed: {
title: function() {
return this.$store.state.message;
}
}
}
I have the following mutation:
FETCHMESSAGE: function (context, type)
{
var data = json.type; // Get the data depending on the type passed in
// COMMIT THE DATA INTO THE STORE
}
And I use it as the following:
<MessageApp message="welcome"></MessageApp>
This works for the most part and the correct message is displayed. The issue is when I have multiple instances of MessageApp being called on the same page. They both show the same message (of the last message) being called. E.g.
<MessageApp message="welcome"></MessageApp>
<MessageApp message="goodbye"></MessageApp>
They will each show the goodbye message. I know why this is happening but is it possible to have multiple instances of the store so that this does not happen?
Vuex is "a centralized store for all the components in an application," as the docs say.
So imagine that you have a variable (or many) which you can use and change from all your components.
Also when you want to get properties from state, it is recommended to use getters.
I can't understand what you want to do, but if you want, you can have multiple states, getters, mutations and actions and use them as modules in the store (read more). See below example from Vuex docs:
const moduleA = {
state: { title: '' },
mutations: { changeTitle(state, payload) { state.title = payload } },
actions: { changeTitle({commit}, payload) { commit('changeTitle', payload) } },
getters: { getTitle(state) { return state.title } }
}
const moduleB = {
state: { title: '' },
mutations: { changeTitle(state, payload) { state.title = payload } },
actions: { changeTitle({commit}, payload) { commit('changeTitle', payload) } },
getters: { getTitle(state) { return state.title } }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> `moduleA`'s state
store.state.b // -> `moduleB`'s state

"Simulate" mutations in vuex

import { remoteSettings } from 'somewhere';
const store = {
state: {
view: {
foo: true
}
},
mutations: {
toggleFoo(state) {
state.view.foo = !state.view.foo;
}
},
actions: {
async toggleFoo({ state, commit }) {
commit('toggleFoo');
await remoteSettings.save(state);
}
}
};
Say I have a simple store like this. toggleFoo action applies the mutation, then saves the new state by making an async call. However, if remoteSettings.save() call fails, local setting I have in the store and remote settings are out of sync. What I really want to achieve in this action is something like this:
async toggleFoo({ state, commit }) {
const newState = simulateCommit('toggleFoo');
await remoteSettings.save(newState);
commit('toggleFoo');
}
I'd like to get the new state without actually committing it. If remote call succeeds, then I'll actually update the store. If not, it's going to stay as it is.
What's the best way to achieve this (without actually duplicating the logic in the mutation function)? Maybe "undo"? I'm not sure.
One way of doing this would be: (credit to #Bert for correcting mistakes)
Store the old state using const oldState = state; before committing the mutation.
Wrap the async call in a try-catch block.
If the remoteSettings fails it will pass the execution to catch block.
In the catch block commit a mutation to reset the state.
Example:
const store = {
state: {
view: {
foo: true
}
},
mutations: {
toggleFoo(state) {
state.view.foo = !state.view.foo;
},
resetState(state, oldState){
//state = oldState; do not do this
//use store's instance method replaceState method to replace rootState
//see : https://vuex.vuejs.org/en/api.html
this.replaceState(oldState)
}
},
actions: {
async toggleFoo({ state, commit }) {
const oldState = JSON.parse(JSON.stringify(state)); //making a deep copy of the state object
commit('toggleFoo');
try {
await remoteSettings.save(newState);
//commit('toggleFoo'); no need to call this since mutation already commited
} catch(err) {
//remoteSettings failed
commit('resetState', oldState)
}
}
}
};
Borrowing code from #VamsiKrishna (thank you), I suggest an alternative. In my opinion, you want to send the changes to the server, and update the local state on success. Here is a working example.
To prevent duplicating logic, abstract the change into a function.
console.clear()
const remoteSettings = {
save(state){
return new Promise((resolve, reject) => setTimeout(() => reject("Server rejected the update!"), 1000))
}
}
function updateFoo(state){
state.view.foo = !state.view.foo
}
const store = new Vuex.Store({
state: {
view: {
foo: true
}
},
mutations: {
toggleFoo(state) {
updateFoo(state)
},
},
actions: {
async toggleFoo({ state, commit }) {
// Make a copy of the state. This simply uses JSON stringify/parse
// but any technique/library for deep copy will do. Honestly, I don't
// think you would be sending the *entire* state, but rather only
// what you want to change
const oldState = JSON.parse(JSON.stringify(state))
// update the copy
updateFoo(oldState)
try {
// Attempt to save
await remoteSettings.save(oldState);
// Only commit locally if the server OKs the change
commit('toggleFoo');
} catch(err) {
// Otherwise, notify the user the change wasn't allowed
console.log("Notify the user in some way that the update failed", err)
}
}
}
})
new Vue({
el: "#app",
store,
computed:{
foo(){
return this.$store.state.view.foo
}
},
mounted(){
setTimeout(() => this.$store.dispatch("toggleFoo"), 1000)
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.0.1/vuex.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.9/vue.js"></script>
<div id="app">
<h4>This value never changes, because the server rejects the change</h4>
{{foo}}
</div>