Vue component doesn´t rerender after vuex state change - vue.js

My vue component doesn´t change a child component when the state of vuex changes.
I have a list of slots for items and want to render items depending on a vuex state variable.
When you click on a "item"-component it sets the selectedItem state variable. Another component watches this variable and sets equippedItems variable with new data.
But when I try to change an item it doesn't show up, even though the state seems to change in Vuex.
I have set up the vuex store like that:
const state = {
equippedItems:
[
{
name: 'Item 1',
strength: 3,
itemType: 1,
rarity: 3
},
{
name: 'Item 2',
strength: 40,
itemType: 2,
rarity: 2
}
],
selectedItem: null
}
const getters = {
getEquippedItems: (state) => state.equippedItems,
getSelectedItem: (state) => state.selectedItem
}
const mutations = {
changeSelectedItem: (state, newItem) => {
state.selectedItem = newItem;
},
changeEquippedItems: (state, parameters) => {
state.equippedItems[parameters[0]] = parameters[1];
}
}
const actions = {
setSelectedItem({ commit }, index) {
commit('changeSelectedItem', index);
},
setNewEquipment({ commit }, parameters) {
commit('changeEquippedItems', parameters);
}
}
export default {
state,
getters,
actions,
mutations
}
Then I have a component that sets the items according to the equippedItems variable
import { mapGetters, mapActions } from 'vuex';
import Item from '../Item';
export default {
name: 'equipped-items',
components: {
Item
},
props: [],
data() {
return {
chooseHead: false,
}
},
computed: {
...mapGetters(['getEquippedItems', 'getItems', 'getSelectedItem'])
},
methods: {
...mapActions(['setNewEquipment']),
chooseNewHead() {
this.chooseHead = !this.chooseHead;
}
},
watch: {
getSelectedItem: function () {
if (this.chooseHead) {
this.setNewEquipment([0, this.getSelectedItem]);
}
}
}
}
<section class="equipped-items">
<div #click="chooseNewHead" class="head equipSlot">
<Item v-if="getEquippedItems[0]" :passedItem="getEquippedItems[0]" :parent="'equip'"> </Item>
</div>
<div class="body equipSlot">
<Item v-if="getEquippedItems[1]" :passedItem="getEquippedItems[1]"></Item>
</div>
</section>
Then there is the item component which sets the vuex variable selectedItem on click.
import { mapActions } from 'vuex';
export default {
name: 'Item',
props: ['passedItem', 'parent'],
methods: {
...mapActions(['setSelectedItem']),
selectedItem() {
if (this.parent != 'equip') {
this.setSelectedItem(this.passedItem);
}
}
}
}
It renders fine the first time when the page is loading, but doesn't change the new passed item to the item-component.
Thanks in advance

There a couple mistakes in your code:
1- I couldn't identify where you call/trigger an event to your selectedItem() method in your component.
2-(This is an extra) If you want to append objects to an array injavascript you just use array.push(odject), so i would suggest you to change your changeEquippedItems mutation to:
changeEquippedItems: (state, parameters) => {
state.equippedItems.push(parameters);
}

Related

Why action of Vuex returns a promise<pending>?

I have an action in Vuex actions which commit a mutation that it take a payload from the component, that is a number of the index for returning an object, it works fine on Vuex js file meaning that shows the selected item on the console, as I said it gets index from the payload,
but on the component, it gives me Promise <Pending>, why that's happening? for now, I do not use any API for my Nuxt/Vue app, but I will, and for now, I just want to know why this is happening and what is the best solution for solving this
Here my Vuex codes:
export const state = () => ({
articles: [
{
uid: 0,
img: 'https://raw.githubusercontent.com/muhammederdem/mini-player/master/img/1.jpg',
link: '/articles/1',
},
{
uid: 1,
img: 'https://raw.githubusercontent.com/muhammederdem/mini-player/master/img/2.jpg',
link: '/articles/2',
},
],
})
export const getters = {
getArticles(state) {
return state.articles
},
}
export const mutations = {
getSpeceficArticle(state, payload) {
return state.articles[payload]
},
}
export const actions = {
getSpeceficArticle({ commit }, payload) {
commit('getSpeceficArticle', payload)
},
}
and here my component codes:
<template>
<div class="article">
{{ getSpeceficArticle() }}
<div class="article__banner">
<img src="" alt="" />
</div>
<div class="article__text">
<p></p>
</div>
</div>
</template>
<script>
export default {
name: 'HomeArticlesArticle',
data() {
return {
item: '',
}
},
// computed: {},
methods: {
async getSpeceficArticle() {
return await this.$store.dispatch('articles/getSpeceficArticle', 0)
},
},
}
</script>
actions are used to update the state they are like mutations but the main difference between them is that actions can include some asynchronous tasks, if you want to get a specific article at given index you should use a getter named getArticleByIndex :
export const getters = {
getArticles(state) {
return state.articles
},
getArticleByIndex:: (state) => (index) => {
return state.articles[index]
}
}
then define a computed property called articleByIndex :
<script>
export default {
name: 'HomeArticlesArticle',
data() {
return {
item: '',
}
},
computed: {
articleByIndex(){
return this.$store.getters.articles.getArticleByIndex(0)
}
},
methods: {
},
}
</script>
#Mohammad if you find yourself using a lot of getters/actions etc from Vuex and they're starting to get a little wordy, you can bring in mapGetters from Vuex and rename your calls to something a little more convenient. So your script would become,
<script>
import { mapGetters } from 'vuex'
export default {
name: 'HomeArticlesArticle',
data() {
return {
item: '',
}
},
computed: {
articleByIndex(){
return this.getArticleByIndex(0)
}
},
methods: {
...mapGetters({
getArticleByIndex: 'articles/getArticleByIndex',
})
},
}
</script>
You can add ...mapGetters, ...mapActions to your computed section also.
since there is no web service call in vuex action, try to remove async and await keywords from the component.
Later when you add a webservice call than you can wrap action body in new Promise with resolve and reject and then you can use async and await in component. let me know if this works for you.

Vuex action payload is undefined

I have a component that looks like this(simplified):
<script>
import { mapActions } from 'vuex';
import router from '#/router';
import { bindingService } from '#/_services/binding.service';
export default {
props: {
serialNumber: { type: String, default: ' ' }
},
data: () => ({
subscriptions: ['Loading...'],
vin: null,
}),
computed: {
splittedSerialNumber() {
return this.serialNumber.split(' ')[0];
}
},
created() {
//fetch some data
bindingService.fetchSomeData('xxx').then((data) => {
this.vin = data;
});
},
methods: {
...mapActions('binding', ['setDeviceSerialNumber', 'setVehicleIdentificationNumber']),
cancel() {
router.push('/home');
},
ok() {
console.log(this.vin); //this console.log outputs valid data
this.setVehicleIdentificationNumber(this.vin);
},
},
};
</script>
Then I have my store that look like this(simplified):
const state = {
vehicleIdentificationNumber: null,
};
const actions = {
setVehicleIdentificationNumber({ commit }, { vin }) {
console.log(vin); // this console.log outputs undefined
commit('SET_VEHICLE_IDENTIFICATION_NUMBER', vin);
}
};
const mutations = {
SET_VEHICLE_IDENTIFICATION_NUMBER(state, vin) {
state.vehicleIdentificationNumber = vin;
},
};
const binding = {
namespaced: true,
state,
actions,
mutations,
};
export default binding;
I'm even more confused because I've been using pretty much the same format of actions and mutations in this project and they work.
I'm out of ideas and looking forward to any kind of input :)
In your setVehicleIdentificationNumber method on the component, you are passing in the vin as an integer argument.
In the action, the param is an object: { vin }.
Change the action param to vin, or pass in an object in the component: { vin: this.vin }
I think the problem here is that your vin property isn't reactive because you initialized it with a null value, but you're changing it to an object. Try this:
bindingService.fetchSomeData('xxx').then((data) => {
Vue.set(this, 'vin', data)
});
Of course, you'll need to import Vue from 'vue'
You should pass the data to the action like this:
actions: {
myAction( store, payload = {myCustomKey: 'my value 1'} ){
store.commit('myCustomMutation', payload.myCustomKey);
}
}
And later уоu can call the action with or without the data:
this.$store.dispatch('myAction');
this.$store.dispatch('myAction', 'my value 2');

Iterating over a Vuex store object

I'm new to Vue.js and Vuex and trying out a sample app.
This is the scenario-
I have a store module for notifications which stores the notifications in an object with a given name as its key.
{
'message1': {
type: 'info',
message: 'This is an info message.',
isShown: true,
},
'message2': {
type: 'success',
message: 'This is a success message.',
isShown: true,
},
'message3': {
type: 'error',
message: 'This is an error message.',
isShown: true,
}
}
And this is my Vuex module that handles notification-
const state = {
notifications: {},
};
const mutations = {
setNotification(state, { message, type, name }) {
state.notifications[name] = {
message,
type,
isShown: true,
}
},
removeNotification(state, name) {
delete state.notifications[name];
}
};
const actions = {
async showNotification(context, options) {
await context.commit('setNotification', options);
},
async removeNotification(context, name) {
await context.commit('removeNotification', name);
}
}
const getters = {
isNotificationShown: (state, getters) => {
return getters.getNotificationMessageList.length > 0;
},
getNotificationMessageList: state => {
return state.notifications;
},
}
export default {
state,
actions,
mutations,
getters,
}
And this is my component-
<template>
<div v-if="isShown">
<div v-for="(notice, name, index) in notificationMessageList" :key="name">
{{ index }} - {{ notice.type }} - {{ notice.message}}
</div>
</div>
</template>
<script>
export default {
computed: {
isShown() {
return this.$store.getters.isNotificationShown;
},
notificationMessageList() {
return this.$store.getters.getNotificationMessageList;
},
},
};
</script>
I checked with the Vue Development tool and found that the store does get updated and so does the component with the notification messages that I'm passing to the store. But the component is not being rendered. But if I use the same data by hardcoding it in the component, it works.
I'm not sure if this is the right way to connect the Vuex store to a component.
It's Vue reactivity problem. You need to update the reference to make Vue reactive. You can use JSON.parse(JSON.stringify()) or use ES6 syntax:
const mutations = {
setNotification(state, { message, type, name }) {
state.notifications = {
...state.notifications,
[name]: {
message,
type,
isShown: true
}
}
},
removeNotification(state, name) {
const newNotifications = {...state.notifications}
delete newNotifications[name]
state.notifications = newNotifications
}
};

Rerender component after state change vue.js

I am working with NuxtJS and VueJS. I'm having a problem with a component not re-rendering after the state changed.
index.js file
Vue.use(Vuex)
const state = {
productsHome: [],
accessToken: {},
collections: {},
product: {},
cart: {},
}
const getters = {
productForHomepage (state) {
return state.productsHome
},
productForPdp (state) {
return state.product
},
cart (state){
return state.cart
}
}
const actions = {
nuxtServerInit (context) {
//good place to set language
},
GET_HOME(){
api.getHomepageProducts().then(response => {
this.commit('setHomeProducts', response.data)
})
},
GET_PDP(sth){
api.findBySlug(this.app.router.history.current.params.slug).then(response => {
this.commit('setPDPData', response.data)
})
},
ADD_TO_CART(store, id){
api.addToCart(id).then(res => {
store.commit('updateCart', res.data)
})
}
}
const mutations = {
setHomeProducts(state, data){
state.productsHome = data
},
setPDPData(state, data){
state.product = data[0]
},
updateCart(state, data){
for (var optbox of data) {
state.cart[optbox.id] = optbox;
}
// state.cart.set('iteams', 'count', 1)
}
}
const createStore = () => {
return new Vuex.Store({
state,
getters,
mutations,
actions
});
}
export default createStore;
and this is the component
<template>
<div>
<div class="content">
<p>
This is cart
</p>
{{ cart }}
</div>
</div>
</template>
<script>
export default {
data() {
return {
cart: this.$store.state.cart
}
},
watch: {
cart: function(val){
cart = this.$store.state.cart
}
},
methods: {
updateCart: function(){
console.log(this)
}
}
}
</script>
When you do this:
data() {
return {
cart: this.$store.state.cart
}
}
You initilise the data with the value of the cart state, but it won't keep changing when the cart state changes, it's a one time deal, as you can see in this JSFiddle
What you actually want to do is use a computed:
computed: {
cart(){
return this.$store.state.cart
}
}
Now whenever cart state changes in your store, so too will the value of cart in your component.
And here's the JSFiddle for that: https://jsfiddle.net/craig_h_411/zrbk5x6q/

Changing a vuex state from a different component?

I have a component (modal) which relies on a store. The store has the state of the modal component - whether it is active or not.
I need to be able to call this modal to open from other components or even just on a standard link. It opens by adding an .active class.
How can I change the state of the store - either by calling the stores action or calling the modal components method (which is mapped to the store).
Modal Store:
class ModalModule {
constructor() {
return {
namespaced: true,
state: {
active: false,
},
mutations: {
toggleActive: (state) => {
return state.active = ! state.active;
},
},
actions: {
toggleActive({ commit }) {
commit('toggleActive');
},
},
getters: {
active: state => {
return state.active;
}
}
};
}
}
export default ModalModule;
Vue Component:
<template>
<div class="modal" v-bind:class="{ active: active }">
<div class="modal-inner">
<h1>modal content here</h1>
</div>
<div class="modal-close" v-on:click="this.toggleActive">
X
</div>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
export default {
computed: {
...mapGetters('Modal', [
'active',
])
},
methods: {
...mapActions('Modal', [
'toggleActive',
]),
}
}
</script>
And somewhere else I want to be able to have something like:
<button v-on:click="how to change the state??">OPEN MODAL</button>
Edit:
Here's the store:
import Vuex from 'vuex';
import ModalModule from './ModalModule';
class Store extends Vuex.Store {
constructor() {
Vue.use(Vuex);
super({
modules: {
Modal: new ModalModule(),
}
});
};
}
You do not need an action for your particular usecase . You just just define a mutation as you are just changing the boolean value of a property in a state. Actions are for async functionality. You usecase is just synchronous change of Boolean value
So you can do
<button v-on:click="$store.commit('toggleActive')">OPEN MODAL</button>
EDIT:
Just export a plain object
const ModalModule = {
namespaced: true,
state: {
active: false,
},
mutations: {
toggleActive: (state) => {
return state.active = ! state.active;
},
},
actions: {
toggleActive({ commit }) {
commit('toggleActive');
},
},
getters: {
active: state => {
return state.active;
}
}
}
export default ModalModule;// export the module
Even remove the class based definition of the store
import Vue from 'vue'
import Vuex from 'vuex';
import ModalModule from './ModalModule';
Vue.use(Vuex);
export const store = new Vuex.Store({
modules: {
ModalModule
}
});
And change it like this in you component for mapping of the mutation (<MODULE_NAME>/<MUTATION_NAME>)
...mapMutations([
'ModalModule/toggleActive'
])
You can access the store from your components via this.$store. There you can call your actions and mutations. So
<button v-on:click="$store.commit('your mutation name', true)">OPEN MODAL</button>