Vue store dispatch single property instead whole object - javascript

I was wondering if it is possible, to dispatch only a single property to vuex store instead the whole object. There are no possibilities written in vuex docs. Currently I store object like this:
store.dispatch('currentUser', {
memberData: 'fooBar'
});
Some times I just want to fetch only a single value from database and push it into store. Is there a way to do that?
UPDATE
I think my question is unclear.
Actually I need to access a child nested property of memberData in dispatch, to change only one element of memberData. To be honest, it does not matter what response is. It could be 'foo' as well.

If you review the documentation for dispatch, the 2nd argument payload, can be any type. It doesn't necessarily need to be an object-style dispatch nested in a property:
Dispatch:
store.dispatch('currentUser', response[0]);
Store:
state: {
currentUser: undefined
},
mutations: {
setCurrentUser(state, currentUser) {
state.currentUser = currentUser;
}
},
actions: {
currentUser(context, payload) {
context.commit('setCurrentUser', payload);
}
}
Mutation:
setCurrentUser(state, currentUser) {
state.currentUser = currentUser;
}
Here is a example in action.
Update:
If the goal instead is to merge/update changes, you can use spread in object literals. This is also mentioned in the Vuex documentation for Mutations Follow Vue's Reactivity Rules.
Dispatch:
store.dispatch('currentUser', { systemLanguage: response[0].systemLangId });
Store:
state: {
memberData: { systemLanguage: 'foo' }
},
mutations: {
updateCurrentUser(state, updates) {
state.memberData = { ...state.memberData, ...updates };
}
},
actions: {
currentUser(context, payload) {
context.commit('updateCurrentUser', payload);
}
}
Here is an example of that in action.
Hopefully that helps!

Related

Vuex Mutation running, but component not updating until manual commit in vue dev tools

I have a vue component that I can't get to update from a computed property that is populated from a service call.
Feed.vue
<template>
<div class="animated fadeIn">
<h1 v-if="!loading">Stats for {{ feed.name}}</h1>
<h2 v-if="loading">loading {{ feedID }}</h2>
</div>
</template>
<script>
export default {
data: () => {
return {
feedID: false
}
},
computed: {
feed(){
return this.$store.state.feed.currentFeed
},
loading(){
return this.$store.state.feed.status.loading;
}
},
created: function(){
this.feedID = this.$route.params.id;
var fid = this.$route.params.id;
const { dispatch } = this.$store;
dispatch('feed/getFeed', {fid});
}
}
</script>
That dispatches 'feed/getFeed' from the feed module...
feed.module.js
import { feedStatsService } from '../_services';
import { router } from '../_helpers';
export const feed = {
namespaced: true,
actions: {
getFeed({ dispatch, commit }, { fid }) {
commit('FeedRequest', {fid});
feedStatsService.getFeed(fid)
.then(
feed => {
commit('FeedSuccess', feed);
},
error => {
commit('FeedFailure', error);
dispatch('alert/error', error, { root: true });
}
)
}
},
mutations: {
FeedRequest(state, feed) {
state.status = {loading: true};
state.currentFeed = feed;
},
FeedSuccess(state, feed) {
state.currentFeed = feed;
state.status = {loading: false};
},
FeedFailure(state) {
state.status = {};
state.feed = null;
}
}
}
The feedStatsService.getFeed calls the service, which just runs a fetch and returns the results. Then commit('FeedSuccess', feed) gets called, which runs the mutation, which sets state.currentFeed=feed, and sets state.status.loading to false.
I can tell that it's stored, because the object shows up in the Vue dev tools. state.feed.currentFeed is the result from the service. But, my component doesn't change to reflect that. And there is a payload under mutations in the dev tool as well. When manually commit feed/feedSuccess in the dev tools, my component updates.
What am I missing here?
In the same way that component data properties need to be initialised, so too does your store's state. Vue cannot react to changes if it does not know about the initial data.
You appear to be missing something like...
state: {
status: { loading: true },
currentFeed: {}
}
Another option is to use Vue.set. See https://vuex.vuejs.org/guide/mutations.html#mutations-follow-vue-s-reactivity-rules...
Since a Vuex store's state is made reactive by Vue, when we mutate the state, Vue components observing the state will update automatically. This also means Vuex mutations are subject to the same reactivity caveats when working with plain Vue
Hey for all the people coming to this and not being able to find a solution. The following was what worked for me:
Declaring base state:
state: {
mainNavData: [],
}
Then I had my action which is calling the now fixed mutation:
actions : {
async fetchMainNavData({ commit }) {
var response = await axios.get();
commit('setMainNavData', response));
},
};
Now my mutation is calling this updateState() function which is key to it all
mutations = {
setMainNavData(state, navData) {
updateState(state, 'mainNavData', navData);
},
};
This is what the updateState function is doing which solved my issues.
const updateState = (state, key, value) => {
const newState = state;
newState[key] = value;
};
After adding updateState() my data reactively showed up in the frontend and I didn't have to manually commit the data in Vue tools anymore.
please note my store is in a different file, so its a little bit different.
Hope this helps others!
Sometimes updating property that are not directly in the state is the problem
{
directprop: "noProblem",
indirectParent: {
"test": 5 // this one has a problem but works if we clone the whole object indirectParent
}
}
but it is a temporary solution, it should help you to force update the state and discover what is the real problem.

Assigning Axios fetched data to Vuex State from Vuex Action

As per the Axios documentation, I concurrently fetch two data sources from my backed (block.json and type.json) within actions of my Vuex store. In my Vuex State, I declare myBlocks and myTypes as data. The data is fetched fine, but I cannot seem to assign the fetched data to the variables in the Vuex state. I seem to have troubles referencing the state, because console.log(state.sample) yields undefined rather than foo. However, console.log(state) yields the following as in the photograph below. Any leads would be great.
state: {
sample: 'foo',
myBlocks: [],
myTypes: []
},
actions: {
fetchElementColors: function(state) {
function getElementBlockColors() { return axios.get('/element-data/block.json'); }
function getCategoryDataColors() { return axios.get('/element-data/type.json'); }
axios.all([getElementBlockColors(), getCategoryDataColors()])
.then(axios.spread(function(blockData, categoryData) {
console.log(state);
console.log(state.sample);
state.myBlocks= blockData.data;
state.myTypes= categoryData.data;
}));
}
}
In your actions, you are not provided with state but context.
So you need to do as follows:
actions: {
fetchElementColors: function(context) {
function getElementBlockColors() { return axios.get('/element-data/block.json'); }
function getCategoryDataColors() { return axios.get('/element-data/type.json'); }
axios.all([getElementBlockColors(), getCategoryDataColors()])
.then(axios.spread(function(blockData, categoryData) {
console.log(context.state);
console.log(context.state.sample);
context.state.myBlocks= blockData.data;
context.state.myTypes= categoryData.data;
}));
}
}
Reference: https://vuex.vuejs.org/guide/actions.html

Updating data from Vuex get a infinite loop in watcher

I have a "dumb" component that just get props from a parent. The user can change a selector which fires an action (using Vuex) to get new data. When this new data has been received I want to pass it to the child and re-render that component with the new data. Unfortutalely I keep getting this warning in my watcher. Please help :slight_smile:
export default {
name: 'bubbles',
props: {
awesomeData: {
type: Array,
required: true
}
},
data () {
return {
title: 'Best component ever'
}
},
watch: {
awesomeData (newData) {
console.log('hello world')
this.refreshSomethingAwesome(newData)
}
},
methods: {
refreshSomethingAwesome (newData) {}
}
}
s
101 hello world
[Vue warn]: You may have an infinite update loop in watcher with expression "awesomeData"
I'm trying a solution pretty much exactly the same as this: Vuex Examples
But can't seem to get it working... hmmm
Found out the reason I was getting the loop was because I was actually trying to sort mutable data from the property (thought it was immutable)
let options = {
children: newData.sort((a, b) => a.value - b.value)
}
I changed it to something like:
const sortedNewData = [...newData].sort((a, b) => a.value - b.value)
let options = {
children: sortedNewData
}
Note: To prevent this across my app, I might wrap my initial state in a Map from immutable.js
import { Map, fromJS } from 'immutable'
const initialState = Map({
awesomeData: fromJS([])
})
Helpful article: alligator.io

Can I do dispatch from getters in Vuex

Fiddle : here
I am creating a webapp with Vue 2 with Vuex. I have a store, where I want to fetch state data from a getter, What I want is if getter finds out data is not yet populated, it calls dispatch and fetches the data.
Following is my Vuex store:
const state = {
pets: []
};
const mutations = {
SET_PETS (state, response) {
state.pets = response;
}
};
const actions = {
FETCH_PETS: (state) => {
setTimeout(function() {
state.commit('SET_PETS', ['t7m12qbvb/apple_9', '6pat9znxz/1448127928_kiwi'])
}, 1000)
}
}
const getters = {
pets(state){
if(!state.pets.length){
state.dispatch("FETCH_PETS")
}
return state.pets
}
}
const store = new Vuex.Store({
state,
mutations,
actions,
getters
});
But I am getting following error:
Uncaught TypeError: state.dispatch is not a function(…)
I know I can do this, from beforeMount of Vue component, but I have multiple components which uses same Vuex store, so I have to do it in one of the components, which one should that be and how will it impact other components.
Getters can not call dispatch as they are passed the state not context of the store
Actions can call state, dispatch, commit as they are passed the context.
Getters are used to manage a 'derived state'.
If you instead set up the pets state on the components that require it then you would just call FETCH_PETS from the root of your app and remove the need for the getter
I know this is an older post and I'm not sure if this is good practice, but I did the following to dispatch from a getter in my store module:
import store from "../index"
And used the store inside my getter like this:
store.dispatch("moduleName/actionName")
I did this to make sure data was made available if it was not already present.
*edit:
I want you to be aware of this: Vue form - getters and side effects
This is related to #storsoc note.
If you need to dispatch from your getter you probably are already implementing your state wrong. Maybe a component higher up should already have fetched the data before (state lifting). Also please be aware that getters should only be used when you need to derive other data from the current state before serving it to your template otherwise you could call state directly: this.$store.state.variable to use in methods/computed properties.
Also thing about your lifecycle methods.. you could for example in your mounted or created methods check if state is set and otherwise dispatch from there. If your getter / "direct state" is inside a computed property it should be able to detect changes.
had the same Problem.. also wanted all Vue-Instances to automaticly load something, and wrote a mixin:
store.registerModule('session', {
namespaced: true,
state: {
session: {hasPermission:{}},
sessionLoaded:false
},
mutations: {
changeSession: function (state, value)
{
state.session = value;
},
changeSessionLoaded: function (state)
{
state.sessionLoaded = true;
}
},
actions: {
loadSession(context)
{
// your Ajax-request, that will set context.state.session=something
}
}
});
Vue.mixin({
computed: {
$session: function () { return this.$store.state.session.session; },
},
mounted:function()
{
if(this.$parent==undefined && !this.$store.state.session.sessionLoaded)
{
this.$store.dispatch("session/loadSession");
this.$store.commit("changeSessionLoaded");
}
},
});
because it loads only one per vue-instance and store and it it inlcuded automaticly in every vue-instance, there is no need to define it in every main-app
I use a getter to configure a dynamic page. Essentially, something like this:
getter: {
configuration: function () {
return {
fields: [
{
component: 'PlainText',
props: {},
setPropsFromPageState: function (props, pageState, store) {
// custom logic
}
}
]
};
}
}
Then in the page component, when I am dynamically setting the props on a dynamic component, I can call the setPropsFromPageState(field.props, this.details, this.$store) method for that component, allowing logic to be set at the config level to modify the value of the props being passed in, or to commit/dispatch if needed.
Basically this is just a callback function stored in the getter that is executed in the component context with access to the $store via it.

redux how to get or store computed values

How should I implement in redux computed values based on it's current state?
I have this for an example a sessionState
const defaultState = {
ui: {
loading: false
}, metadata: { },
data: {
id: null
}
}
export default function sessionReducer(state = defaultState, action) {
switch(action.type) {
case STORE_DATA:
return _.merge({}, state, action.data);
case PURGE_DATA:
return defaultState;
default:
return state;
}
}
Say for example I want to get if the session is logged in, I usually do right now is sessionState.data.id to check it, but I want to know how I can do sessionState.loggedIn or something?
Can this do?
const defaultState = {
ui: {
loading: false
}, metadata: { },
data: {
id: null
},
loggedIn: () => {
this.data.id
}
}
or something (that's not tested, just threw that in). In this example it looks simple to just write .data.id but maybe when it comes to computations already, it's not good to write the same computations on different files.
Adding methods to state object is a bad idea. State should be plain objects. Keep in mind, that some libraries serialize the app state on every mutation.
You can create computing functions outside the state object. They can receive state as an argument. Why should they be state's methods?
const defaultState = {
ui: {
loading: false
}, metadata: { },
data: {
id: null
}
}
const loggedIn = (state) => {
//your logic here
}
In your particular example, the calculated result is incredibly simple and fast to calculate so you can calculate it on demand each time. This makes it easy to define a function somewhere to calculate it like #Lazarev suggested.
However, if your calculations become more complicated and time consuming, you'll want to store the result somewhere so you can reuse it. Putting this data in the state is not a good idea because it denormalizes the state.
Luckily, since the state is immutable, you can write a simple pure function to calculate a result and then you can use memoization to cache the result:
const isLoggedIn = memoize(state => state.login.userName && state.login.token && isValidToken (state.login.token));
Lastly, if you want to use methods on your store state, you can use Redux Schema (disclaimer: I wrote it):
const storeType = reduxSchema.type({
ui: {
loading: Boolean
}, metadata: { },
data: {
id: reduxSchema.optional(String)
},
loggedIn() {
return Boolean(this.data.id);
}
});

Categories

Resources