Issue updating state reactively in Vuex - javascript

Creating a Vuejs application whereby I use Vuex for state management between the components.In Vuex store, I have an action that fetches some data from an API (which works fine) then populate it to the state (via a mutation). Next, I pass the updated state to the component using getters.
Am having a problem populating data to the state (reactive manner) from the action (via mutation). In the DOM when I log the getter am getting an empty string.
Vuex Store
const getDefaultState = () => {
return {
clientDeposit: ''
}
}
//state
const state = getDefaultState();
//getters
const getters = {
getDeposit: (state) => state.clientDeposit
}
//actions
const actions = {
fetchClients({ commit}) {
const clientId ="23"
axios.post('/api/motor/fetchClients', {
ClientId: clientId,
})
.then((response)=> {
//console.log(response); //returns data
const deposit = response.data;
commit('setDeposit', deposit);
})
}
}
//mutations
const mutations = {
setDeposit: (state, value) => (state.clientDeposit = value)
}
export default {
state,
getters,
actions,
mutations
}
Component
<template>
<div>
<button onclick="fetchClients()">Fetch Clients</button>
Deposit (Via computed property) : {{ fetchDeposit }}
Deposit (from getter) : {{ getDeposit }}
</div>
</template>
<script>
import { mapGetters , mapActions } from "vuex";
import axios from "axios";
export default {
name: "",
data() {
return {
}
},
computed: {
...mapGetters([
"getDeposit"
]),
fetchDeposit(){
return this.getDeposit
},
},
methods:{
...mapActions([
"fetchClients"
])
}
};
</script>
<style scoped>
</style>

Related

How to set my state from vuex to it's "original" form? [duplicate]

My state in vuex store is huge.
Is there a way to reset all the data in state in one go, instead of manually setting everything to null?
I have just found the great solution that works for me.
const getDefaultState = () => {
return {
items: [],
status: 'empty'
}
}
// initial state
const state = getDefaultState()
const actions = {
resetCartState ({ commit }) {
commit('resetState')
},
addItem ({ state, commit }, item) { /* ... */ }
}
const mutations = {
resetState (state) {
// Merge rather than replace so we don't lose observers
// https://github.com/vuejs/vuex/issues/1118
Object.assign(state, getDefaultState())
}
}
export default {
state,
getters: {},
actions,
mutations
}
Thanks to Taha Shashtari for the great solution.
Michael,
Update after using the below solution a bit more
So it turns out that if you use replaceState with an empty object ({}) you end up bricking reactivity since your state props go away. So in essence you have to actually reset every property in state and then use store.replaceState(resetStateObject). For store without modules you'd essentially do something like:
let state = this.$store.state;
let newState = {};
Object.keys(state).forEach(key => {
newState[key] = null; // or = initialState[key]
});
this.$store.replaceState(newState);
Update (from comments): What if one needs to only reset/define a single module and keep the rest as they were?
If you don't want to reset all your modules, you can just reset the modules you need and leave the other reset in their current state.
For example, say you have mutliple modules and you only want to reset module a to it's initial state, using the method above^, which we'll call resetStateA. Then you would clone the original state (that includes all the modules before resetting).
var currentState = deepClone(this.state)
where deepClone is your deep cloning method of choice (lodash has a good one). This clone has the current state of A before the reset. So let's overwrite that
var newState = Object.assign(currentState, {
a: resetStateA
});
and use that new state with replaceState, which includes the current state of all you modules, except the module a with its initial state:
this.$store.replaceState(newState);
Original solution
I found this handy method in Vuex.store. You can clear all state quickly and painlessly by using replaceState, like this:
store.replaceState({})
It works with a single store or with modules, and it preserves the reactivity of all your state properties. See the Vuex api doc page, and find in page for replaceState.
For Modules
IF you're replacing a store with modules you'll have to include empty state objects for each module. So, for example, if you have modules a and b, you'd do:
store.replaceState({
a: {},
b: {}
})
You can declare an initial state and reset it to that state property by property. You can't just do state = initialState or you lose reactivity.
Here's how we do it in the application I'm working on:
let initialState = {
"token": null,
"user": {}
}
const state = Vue.util.extend({}, initialState)
const mutations = {
RESET_STATE(state, payload) {
for (let f in state) {
Vue.set(state, f, initialState[f])
}
}
}
I am not sure what you use case is, but I had to do something similar. When a user logs out, I want to clear the entire state of the app - so I just did window.reload. Maybe not exactly what you asked for, but if this is why you want to clear the store, maybe an alternative.
If you do a state = {}, you will remove the reactivity of the properties and your getters mutations will suddenly stop working.
you can have a sub-property like:
state: {
subProperty: {
a: '',
lot: '',
of: '',
properties: '',
.
.
.
}
}
Doing a state.subProperty = {} should help, without losing the reactivity.
You should not have a state too big, break them down to different modules and import to your vuex store like so:
import Vue from 'vue'
import Vuex from 'vuex'
import authorization from './modules/authorization'
import profile from './modules/profile'
Vue.use(Vuex)
export const store = new Vuex.Store({
modules: {
authorization,
profile
}
})
now in your individual files:
// modules/authorization.js
import * as NameSpace from '../NameSpace'
import { someService } from '../../Services/something'
const state = {
[NameSpace.AUTH_STATE]: {
auth: {},
error: null
}
}
const getters = {
[NameSpace.AUTH_GETTER]: state => {
return state[NameSpace.AUTH_STATE]
}
}
const mutations = {
[NameSpace.AUTH_MUTATION]: (state, payload) => {
state[NameSpace.AUTH_STATE] = payload
},
}
const actions = {
[NameSpace.ASYNC_AUTH_ACTION]: ({ commit }, payload) => {
someService.login(payload.username, payload.password)
.then((user) => {
commit(NameSpace.AUTH_MUTATION, {auth: user, error: null})
})
.catch((error) => {
commit(NameSpace.AUTH_MUTATION, {auth: [], error: error})
})
}
}
export default {
state,
getters,
mutations,
actions
}
If you should want to clear the state you can just have a mutation implement:
state[NameSpace.AUTH_STATE] = {
auth: {},
error: null
}
Here's a solution that works in my app. I created a file named defaultState.js.
//defaultState.js
//the return value is the same as that in the state
const defaultState = () => {
return {
items: [],
poles: {},
...
}
}
export default defaultState
And then Where you want to use it
//anywhere you want to use it
//for example in your mutations.js
//when you've gotten your store object do
import defaultState from '/path/to/defaultState.js'
let mutations = {
...,
clearStore(state){
Object.assign(state, defaultState())
},
}
export default mutations
Then in your store.js
import Vue from 'vue';
import Vuex from 'vuex';
import actions from './actions';
import getters from './getters';
import mutations from './mutations'; //import mutations
import state from './state';
Vue.use(Vuex);
export default new Vuex.Store({
actions,
mutations,
state,
getters,
});
and That's it
If you want to reset your entire state you can use the built in replaceState method.
Given a state set in index.js:
const state = { user: '', token: '', products: [] /* etc. */ }
const initialStateCopy = JSON.parse(JSON.stringify(state))
export const store = new Vuex.Store({ state, /* getters, mutations, etc. */ })
export function resetState() {
store.replaceState(initialStateCopy)
}
Then in your vue component (or anywhere) import resetState:
import { resetState } from '#/store/index.js'
// vue component usage, for example: logout
{
// ... data(), computed etc. omitted for brevity
methods: {
logout() { resetState() }
}
}
Based on these 2 answers (#1 #2) I made a workable code.
My structure of Vuex's index.js:
import Vue from 'vue'
import Vuex from 'vuex'
import createPersistedState from 'vuex-persistedstate'
import { header } from './header'
import { media } from './media'
Vue.use(Vuex)
const store = new Vuex.Store({
plugins: [createPersistedState()],
modules: {
header,
media
}
})
export default store
Inside each module we need to move all states into separated var initialState and in mutation define a function resetState, like below for media.js:
const initialState = () => ({
stateOne: 0,
stateTwo: {
isImportedSelected: false,
isImportedIndeterminate: false,
isImportedMaximized: false,
isImportedSortedAsc: false,
items: [],
stateN: ...
}
})
export const media = {
namespaced: true,
state: initialState, // <<---- Our States
getters: {
},
actions: {
},
mutations: {
resetState (state) {
const initial = initialState()
Object.keys(initial).forEach(key => { state[key] = initial[key] })
},
}
}
In Vue component we can use it like:
<template>
</template>
<script>
import { mapMutations } from 'vuex'
export default {
name: 'SomeName',
data () {
return {
dataOne: '',
dataTwo: 2
}
},
computed: {
},
methods: {
...mapMutations('media', [ // <<---- define module
'resetState' // <<---- define mutation
]),
logout () {
this.resetState() // <<---- use mutation
// ... any code if you need to do something here
}
},
mounted () {
}
} // End of 'default'
</script>
<style>
</style>
Call router.go() or this.$router.go()
That will refresh the page and your state will be reset to how it was when the user first loaded the app.
Myself has read above and implemented a solution. could help you as well!!
All objects stored in Vue act as an observable. So if reference of a value is changed/mutated it triggers the actual value to be changed too.
So, Inorder to reset the state the initial store modules has to be copied as a value.
On logging out of an user, the same value has to be assigned for each modules as a copy.
This can be achieved as following:
Step 1: Create a copy of your initial module.
// store.ts
// Initial store with modules as an object
export const initialStoreModules = {
user,
recruitment,
};
export default new Vuex.Store({
/**
* Assign the modules to the store
* using lodash deepClone to avoid changing the initial store module values
*/
modules: _.cloneDeep(initialStoreModules),
mutations: {
// reset default state modules by looping around the initialStoreModules
[types.RESET_STATE](state: any) {
_.forOwn(initialStoreModules, (value: IModule, key: string) => {
state[key] = _.cloneDeep(value.state);
});
},
}
});
Step 2: Call the action to mutate the state to initial state.
// user_action.ts
const logout = ({ commit }: any) => {
commit(types.LOGOUT_INIT);
new UserProxy().logout().then((response: any) => {
router.push({
name: 'login',
});
// reset the state
commit(types.RESET_STATE);
}).catch((err: any) => {
commit(types.LOGOUT_FAIL, err);
});
};
You could take it easy by tiny package: vuex-extensions
Check out the example on CodeSandbox.
Creating Vuex.Store
import Vuex from 'vuex'
import { createStore } from 'vuex-extensions'
export default createStore(Vuex.Store, {
plugins: []
modules: {}
})
Store resets to initial State
// Vue Component
this.$store.reset()
// Vuex action
modules: {
sub: {
actions: {
logout() {
this.reset()
}
}
}
}
You can do this
index.js
...
const store = new Vuex.Store({
modules: {
...
}
})
store.initialState = clone(store.state)
store.resetState = () => {
store.replaceState(store.initialState)
}
export default store
Other place
this.$store.resetState()
function initialState () {
return { /* .. initial state ... */ }
}
export default {
state: initialState,
mutations: {
reset (state) {
// acquire initial state
const s = initialState()
Object.keys(s).forEach(key => {
state[key] = s[key]
})
}
}
}
This is an official recommendation
issue
if you clear your complete vuex store use:
sessionStorage.clear();

TypeError: _vm.removeProductFromCart is not a function in vue/vuex

I am trying to delete an item from the cart. So I should be able to delete the item with the id of the cart.
I have a cartHelper and there I defined my api call:
removeFromCart: function (id, callback = undefined) {
return apiHelper.deleteRequest(
`/carts/${this.cookieValue}/remove-item`,
(response) => {
document.cookie = `${this.cartCookieName}=${response.data.attributes.cart_guid};`;
if (callback) { callback(response); }
},
{
id: id
}
)
},
And later I am calling this function in my Cart component:
methods: {
removeFromCart(id) {
cartHelper.removeFromCart(id, () => {
this.$store.dispatch('removeProductFromCart', id)
});
},
},
And I defined my action like in the below:
export const removeProductFromCart = ({ commit }, id) => {
commit('REMOVE_PRODUCT_FROM_CART', id);
}
And here is my mutation:
export const REMOVE_PRODUCT_FROM_CART = (state, id) => {
state.cart = state.cart.filter(item => {
return item.id !== id;
})
}
But as soon as I click the button, where I am calling removeFromCart in my Cart component, I am getting "TypeError: _vm.removeProductFromCart is not a function" and I couldn't figure out the reason. If you can help me, that would be great.
Edited version---------
Here is my state:
export default {
cart: {
"attributes": {
"items": [],
}
}
And my index.js for store:
import Vue from 'vue';
import Vuex from "vuex";
Vue.use(Vuex);
import state from "./state";
import * as getters from './getters';
import * as mutations from "./mutations";
import * as actions from "./actions";
export default new Vuex.Store({
state,
getters,
mutations,
actions,
});
}
Seems you have been calling removeProductFromCart on #click event which does not exist in you methods. Try calling removeFromCart on #click event.

'unknown action type' with Vuex mapAction

I am new to Vuex and trying to use mapActions to fetch some data from my store, and send it into my component's template. I keep getting the error message
[vuex] unknown action type: getItemDetail but I don't know why.
In my store, the action I'm trying to dispatch is getItemDetail. My full store is
import fetch from './fetch';
const url = 'items';
const defaults = {
id: '',
rating: '',
type: '',
};
const state = { ...defaults, };
const getters = {};
const actions = {
getItemDetail ({ commit, }, itemId) {
fetch.get(`${url}/${itemId}`).then((response) => {
commit('setItemDetail', { ...defaults, ...response.data, });
});
},
};
const mutations = {
setItemDetail (state, item) {
state.item = item;
},
};
export default {
namespaced: true,
state,
getters,
actions,
mutations,
};
In my component I have:
<template>
<div>
<p> {{ itemDetail }} </p>
</div>
</template>
<script>
import { mapActions } from 'vuex';
export default {
computed: {
itemDetail () {
this.getItemDetail('23451');
return this.$store.state;
},
},
methods: {
...mapActions([
'getItemDetail',
]),
},
};
</script>
Any help would be much appreciated!!!
From what I see of your code, you are in a namespaced store module.
To access a namespaced action, you need to map it with the name of the store module as the first parameter (the same should be applied for any mapState or mapGetter) :
methods: {
...mapActions("yourModuleName", [
'getItemDetail',
]),
},

VueJS computed property not updating on DOM

I'm trying to do a file upload with VueJS.
When a file is added to the input field it is buffered and saved in the vuex store.
I'm positive that the state updates, this shows in vue-devtool and I added a button to check it.
The DOM however is not re-rendering on the state change. I tried it both with the buffer array and just a regular string.
(when I click the commit button in vue-dev tools it updates the dom)
Please refer to this screenshot for a demonstration of the issue (this is after selecting a file and clicking the "console log state" button).
Demonstration
Component
<template>
<div id="home">
<h3>Upload Book (pdf)</h3>
<form v-on:submit="">
<input v-on:change="captureFile" type="file" placeholder="Select file..." />
<button type="submit">Upload</button>
<p>
<button v-on:click="consoleLog">Console log state</button>
{{filebuffer}}
</p>
</form>
</div>
</template>
<script>
export default {
name: 'home',
computed: {
filebuffer () {
return this.$store.state.fileBuffer
}
},
methods: {
captureFile (event) {
let file = event.target.files[0]
let reader = new window.FileReader()
reader.readAsArrayBuffer(file)
reader.onloadend = () => {this.$store.dispatch('loadBuffer', reader)}
},
consoleLog () {
console.log(this.$store.state.fileBuffer)
}
}
}
</script>
Store
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
Vue.use(Vuex)
export const store = new Vuex.Store({
strict: true,
state,
mutations: {
saveBuffer (state, payload) {
state.fileBuffer = 'this will not update on the DOM'
console.log('saveBuffer committed', payload)
}
},
actions: {
loadBuffer ({commit}, payload) {
let buffer = Buffer.from(payload.result)
commit('saveBuffer', buffer)
}
}
})
you need to use Getters.
computed: {
filebuffer () {
return this.$store.getters.filebuffer;
}
}
and inside your store file
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex)
// State
const state = {
fileBuffer : '',
}
// Mutate the State
const mutations = {
saveBuffer (state, value) {
state.fileBuffer = value
}
// Access The State
const getters = {
fileBuffer : state => {
return state.fileBuffer
}
}
const actions = {
loadBuffer ({commit}, payload) {
let buffer = Buffer.from(payload.result)
commit('saveBuffer', buffer)
}
}
const module = {
state,
getters,
mutations,
actions
};
export default module;
Hope this help solve your problem .

Mutation committed but state not updated

I have my mutation committed, but the currentCampaign state is not updated instead returns undefined. Below is the screenshot.
This is the store.
import Vuex from 'vuex'
import Vue from 'vue'
import axios from 'axios'
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
campaigns: '',
currentCampaign: ''
},
actions: {
getCampaigns ( {commit} ) {
axios
.get('/api/campaign/history')
.then((response) => {
commit('GET_CAMPAIGNS', {campaigns: response.data});
})
}
},
mutations: {
GET_CAMPAIGNS: (state, {campaigns}) => {
state.campaigns = campaigns
},
getCurrentCampaign (state, {campaign}) {
state.currentCampaign = campaign
}
},
});
I am calling the mutation from component method like so:
methods: {
markAsCurrent (campaign) {
this.$store.commit('getCurrentCampaign', campaign.id)
}
}
What I'm not doing right here?
Thats why you are destructuring a campaign var and passing a number to the mutations, not a object.
Try this
getCurrentCampaign (state, campaign) {
state.currentCampaign = campaign
}

Categories