I am trying to solve a problem in my vuex store. I write two different actions in my store. One action is reactive and the other not. But I need the loadSlidesEach() in reactivity, so the data are updated. I cant find the mistake.
My store:
const store = new Vuex.Store({
state: {
loadedSlides: []
},
mutations: {
setSlides(state, slides) {
state.loadedSlides = slides
},
setSlidesPush(state, slide) {
state.loadedSlides.push(slide)
}
},
getters: {
loadedSlides(state) {
return state.loadedSlides
}
},
actions: {
loadSlides({ commit, getters, state }) {
firebase.database().ref('/slides/').on('value', snapshot => {
const slidesArray = []
const obj = snapshot.val()
for (const key in obj) {
slidesArray.push({ ...obj[key], id: key })
}
commit('setSlides', slidesArray)
})
},
loadSlidesEach({ commit, getters, state }, id) {
firebase.database().ref('/slides/' + id).on('value', snapshot => {
const slide = snapshot.val()
commit('setSlidesPush', { ...slide })
})
}
}
})
My component 1: Array from slides() is reactive
export default {
computed: {
slides() {
return this.$store.getters.loadedSlides
}
},
created() {
this.$store.dispatch('loadSlides')
}
}
My component 2: Array from slides() is not reactive
export default {
computed: {
slides() {
return this.$store.getters.loadedSlides
}
},
created() {
const slides = ['1','2','3']
for (let i = 0; i < slides.length; i++) {
this.$store.dispatch('loadSlidesEach', slides[i])
}
}
}
I think the problem is something with the inital state or the mutation with push(). Any advices?
Update:
The two actions are only different in the mutations. So what is the best way to set the state in vuex? The store get confused if I call the action loadSlidesEach() in a loop.
Don't use await and then together. Use one or another:
loadSlidesEach: async({ commit, getters }, id) => {
const data = await firebase.database().ref('/slides/' + id).once('value')
const slide = data.val()
commit('setSlidesPush', { ...slide })
}
do you try to use mapState from vuex ?:
import {mapState} from 'vuex'
export default {
computed: {
...mapState(['loadedSlides '])
}
}
now you can use loadedSlides in component.
I found a working solution for my problem.
Change the mutation:
setSlidesPush(state, addedSlide) {
const slideIndex = state.loadedSlides.findIndex(slide => slide.id === addedSlide.id)
if (slideIndex !== -1) {
Vue.set(state.loadedSlides, slideIndex, addedSlide)
} else {
state.loadedSlides.push(addedSlide)
}
}
Related
I have a redux slice as following:
export const mealPlanSlice = createSlice({
name: "mealPlanner",
initialState,
reducers: {
addOrUpdatePlan: addOrUpdate,
}
})
export const { addOrUpdatePlan } = mealPlanSlice.actions;
export default mealPlanSlice.reducer;
With a reducer that I am embarrassed to show below (personal project no judging pls). The logic is correct but I am not getting re-renders when the action is dispatched. Redux dev tools shows the correct difference. I thought that by deep-cloning the previous state, mutating that state, and then returning it would be guaranteed I get a re-render after running but apparently not. I am quite confused at this point because I have manually checked that each level has been deep-cloned correctly and fail a shallow equality check with the previous value.
Would someone be able to explain what is going wrong please? I am having this problem with other slices for my project as well, so there must be some underlying issue I am not seeing.
import clone from "just-clone";
export const addOrUpdate = (
state: IMealPlanState,
action: PayloadAction<
{ date: DateString } & {
components: {
recipeId: RecipeUuid;
componentId: ComponentUuid;
servingsIncrease: number;
}[];
}
>
) => {
const newState = clone(state);
const { date, components } = action.payload;
for (const { recipeId, componentId, servingsIncrease } of components) {
if (newState.plan[date]) {
if (recipeId in newState.plan[date]) {
const componentIndex = newState.plan[date][recipeId].findIndex(
(mealPlanItem) => mealPlanItem.componentId === componentId
);
if (componentIndex > -1) {
const component = newState.plan[date][recipeId][componentIndex];
const newServings = component.servings + servingsIncrease;
if (newServings > 0) {
newState.plan[date][recipeId][componentIndex].servings =
newServings;
} else {
newState.plan[date][recipeId].splice(componentIndex, 1);
if (newState.plan[date][recipeId].length === 0) {
delete newState.plan[date][recipeId];
}
}
} else {
newState.plan[date][recipeId].push({
componentId,
servings: servingsIncrease,
});
}
} else {
newState.plan[date][recipeId] = [
{
componentId,
servings: servingsIncrease,
},
];
}
}
}
return newState;
};
For context my store looks like the following
export const store = configureStore<IFullStoreState>({
reducer: {
printing,
plants,
food,
mealPlan,
user,
},
});
For extra context my component that subscribes to this slice looks a bit like
const Recipes = () => {
const mealPlan = useSelector((store) => store.mealPlan.plan);
return ...
}
On further investigation this only happens in a development build, after making a deployment the problem disappears. I am using NextJS.
I was able to fetch data and display them using Nuxt's Fetch API, but I want to utilize Vuex instead.
store/index.js:
import Axios from 'axios'
export const getters = {
isAuthenticated (state) {
return state.auth.loggedIn
},
loggedInUser (state) {
return state.auth.user
}
}
export const state = () => ({
videos: []
})
export const mutations = {
storeVideos (state, videos) {
state.videos = videos
}
}
export const actions = {
async getVideos (commit) {
const res = await Axios.get(`https://api.themoviedb.org/3/movie/popular?api_key=${process.env.API_SECRET}&page=${this.currentPage}`)
commit('storeVideos', res.data)
}
}
pages/test.vue:
<template>
<Moviecards
v-for="(movie, index) in $store.state.videos"
:key="index"
:movie="movie"
:data-index="index"
/>
</template>
<script>
...
fetch ({ store }) {
store.commit('storeVideos')
},
data () {
return {
prevpage: null,
nextpage: null,
currentPage: 1,
pageNumbers: [],
totalPages: 0,
popularmovies: []
}
},
watch: {
},
methods: {
next () {
this.currentPage += 1
}
}
}
...
The array returns empty when I check the Vue Dev Tools.
In fetch(), you're committing storeVideos without an argument, which would set store.state.videos to undefined, but I think you meant to dispatch the getVideos action:
export default {
fetch({ store }) {
// BEFORE:
store.commit('storeVideos')
// AFTER:
store.dispatch('getVideos')
}
}
Also your action is incorrectly using its argument. The first argument is the Vuex context, which you could destructure commit from:
export const actions = {
// BEFORE:
async getVideos (commit) {} // FIXME: 1st arg is context
// AFTER:
async getVideos ({ commit }) {}
}
I'm trying to change the value of a state from false to true but it's not seeming to work. Below I will add my store, component that only gets the state, and the component that should update the state.
//Store
export const store = new Vuex.Store({
state: {
newComment: false,
status: ''
},
mutations: {
updateComments(state, payload) {
state.newComment = payload;
}
}
});
// Component that gets state
<template>
<div class="home">
<h1>{{newComment}}</h1>
<SelectedVideo v-bind:user="user" v-bind:video="videos[0]"/>
</div>
</template>
<script>
import axios from 'axios';
import SelectedVideo from '../components/SelectedVideo.component';
axios.defaults.withCredentials = true;
export default {
name: 'Home',
components: {
SelectedVideo
},
data() {
return {
videos: [],
user: null
}
},
computed: {
newComment() {
return this.$store.state.newComment
}
},
// Component I need to update state
methods: {
updateComments() {
this.$store.dispatch('updateComments', true);
}
},
async createComment(comment, video, user) {
try{
const res = await axios({
method: 'POST',
url: 'http://localhost:8000/api/v1/comments/',
data: {
comment,
video,
user
}
});
if (res.data.status === 'success') {
console.log(res);
// location.reload(true);
this.updateComments();
}
} catch(err) {
I'm successfully getting the state but updating seems to have no affect on state when I try to invoke the function to update.
First make a getter for using state data.
getters:{
get_newComment: state => state.newComment
}
Than implement this into your computed property.
computed: {
newComment() {
return this.$store.getters.get_newComment
}
}
After then use to "Commit" to commit some changes not "dispatch". Dispatch using for calling actions.
I have simple file called _mixin.js which consists of:
const mutations = {
_set(state, data) {
for (let key in data) {
if (key in state) {
state[key] = data[key];
}
}
},
_reset(state) {
const s = initialState();
Object.keys(s).forEach(key => {
state[key] = s[key];
});
}
};
export default {
mutations
};
What I'm trying to do is share this two methods between all existing modules mutation like this:
import _MIXINS from 'store/modules/_mixins';
function initialState() {
return {
id: null,
email: null,
password: null,
name: null,
};
}
const state = initialState();
const mutations = {
..._MIXINS.mutations,
setId(state, id) {
state.id = id;
}
};
The problem is that browser says it cant find function initialState as its not in same file.
Just do like this:
// sharedLogic.js
export function initialState () {
// your logic
}
// store module
import { initialState } from '<pathTo sharedLogic.js>'
const state = initialState();
// mixin module
import { initialState } from '<pathTo sharedLogic.js>'
const mutations = {
...
_reset(state) {
const s = initialState();
Object.keys(s).forEach(key => {
state[key] = s[key];
});
}
};
I'm doing a chart component that will be several times represented on a page.
For that, I'm using Vuex where I have the mutations / actions to get that date, and everything works fine.
In the graph component, in created () I call the action to make the request, and I want to pass the date to the chart.
The state comes as follows -
{
typeChart: [data],
typeChart2: [data2]
}
If I do console.log from the state it appears fine but if using the key returns undefined.
console.log(this.dataChart)
// log - { stacked: Array }
console.log(this.dataChart.stacked);
// log - undefined
The only way I found to pass the state to the chart was to do a timeout but I think that is not the best way to do this.
here's the code I have
component of page -
<template>
<stacked-chart
endpoint="endpoint"
chart="stacked"
:states="['state1', 'state2', 'state3']"
:keys="['key1', 'key2', 'key3']"
/>
</template>
component of chart -
<template>
<div class="box-content-white">
<div class="title"> Gráfico 1 </div> <!-- //! name for chart -->
<column-chart :data="data" :stacked="true" :colors="['#006837', '#FF0000', '#000']"></column-chart>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import { mapActions } from 'vuex';
export default {
props: {
endpoint: String,
chart: String,
states: Array,
keys: Array
},
data() {
return {
data: []
}
},
created() {
this.fetchReporting({endpoint: this.endpoint, chart: this.chart, states: this.states, keys: this.keys, formData: { state: 'negotiation' }});
console.log(this.dataChart);
console.log(this.dataChart[this.chart]);
setTimeout(() =>{
this.data = this.dataChart[this.chart];
}, 150);
},
methods: {
...mapActions({
fetchReporting: 'fetchReporting'
})
},
mounted() {
this.data = this.dataChart[this.chart];
},
computed: {
...mapGetters({
dataChart: 'dataChart'
})
},
watch: {
}
}
</script>
file with vuex -
import axios from 'axios';
const state = {
dataChart: {}
}
const mutations = {
'ADD_DATA_CHART'(state, data) {
state.dataChart[data.key] = [];
[].forEach.call(data.states, (s, i) => {
let obj = {};
obj.name = s;
obj.data = [];
[].forEach.call(data.value, d => {
obj.data.push([d.name, d[data.keys[i]].toFixed(2)]);
});
state.dataChart[data.key].push(obj);
});
}
}
const actions = {
fetchReporting({state, commit}, response) {
axios.post(response.endpoint, response.formData)
.then(({data}) => {
commit('ADD_DATA_CHART', {key: response.chart, value: data, states: response.states, keys: response.keys})
}).catch(err => {
console.log(err);
});
}
}
const getters = {
dataChart: state => {
return state.dataChart;
}
}
export default {
state,
mutations,
actions,
getters
}
any suggestion?
You need to wait for you action fetchReporting to complete. Either by using async/await or by using promise/resolve