Change state using Mutation VueJS? - javascript

So i have a Vuex Store setup which returns me headers and desserts. Inside my desserts i have a property setup named display which is initially false. I have a component Row which i am importing inside my parent component named Table. Row Components accepts a couple of props i.e. Name and Display. The display prop is what is returned from desserts from the vuex store. I am trying to add a mutation such that on Click of the icon in my Row component, the display.laptop can be toggled to true and false. I have setup the toggleLaptopDisplay but i keep getting cannot read property laptop of undefined.
Please look at the complete CodeSandbox.
Here is the complete code:
The Vuex Store:-
export default new Vuex.Store({
state: {
headers: [{ text: "Dessert (100g serving)", value: "name" }],
desserts: [
{ name: "Lollipop", display: { laptop: true } },
{ name: "Marshamallow", display: { laptop: false } }
]
},
getters: {
getHeaders: state => state.headers,
getDesserts: state => state.desserts
},
mutations: {
toggleLaptopDisplay(state) {
state.desserts.display.laptop = !state.desserts.display.laptop;
}
}
});
This is the Table Component:-
<template>
<v-data-table :headers="getHeaders" :items="getDesserts" hide-actions select-all item-key="name">
<template v-slot:headers="props">
<tr>
<th v-for="header in props.headers" :key="header.text">{{ header.text }}</th>
</tr>
</template>
<template v-slot:items="props">
<tr>
<Row :name="props.item.name" :display="props.item.display"/>
</tr>
</template>
</v-data-table>
</template>
<script>
import { mapGetters } from "vuex";
import Row from "./Row";
export default {
components: {
Row
},
data() {
return {};
},
computed: {
...mapGetters({
getHeaders: "getHeaders",
getDesserts: "getDesserts"
})
}
};
</script>
This is the Row component:-
<template>
<div>
{{name}}
<v-icon #click="toggleLaptopDisplay" :color="display.laptop ? 'info': '' ">smartphone</v-icon>
</div>
</template>
<script>
import { mapMutations } from "vuex";
export default {
props: {
name: String,
display: Object
},
methods: {
...mapMutations({
toggleLaptopDisplay: "toggleLaptopDisplay"
})
}
};
</script>
Any help will be appreciated. Thank you :)

Few things to achieve what you want:
In your Row.vue add a method to select appropriate element:
<v-icon #click="toggleChange" :color="display.laptop ? 'info': '' ">smartphone</v-icon>
then in methods, create a method that will pass name of the element as payload:
methods: {
toggleChange() {
this.toggleLaptopDisplay(this.name)
},
...mapMutations({
toggleLaptopDisplay: "toggleLaptopDisplay"
})
}
finally, in store.js, use the payload and mutate selected element:
mutations: {
toggleLaptopDisplay(state, payload) {
state.desserts.find(dessert => dessert.name === payload).display.laptop = !state.desserts.find(dessert => dessert.name === payload).display.laptop
}
}
Edited Example

Related

Vue not reacting to a computed props change

I am using the Vue composition API in one of my components and am having some trouble getting a component to show the correct rendered value from a computed prop change. It seems that if I feed the prop directly into the components render it reacts as it should but when I pass it through a computed property it does not.
I am not sure why this is as I would have expected it to be reactive in the computed property too?
Here is my code:
App.vue
<template>
<div id="app">
<Tester :testNumber="testNumber" />
</div>
</template>
<script>
import Tester from "./components/Tester";
export default {
name: "App",
components: {
Tester,
},
data() {
return {
testNumber: 1,
};
},
mounted() {
setTimeout(() => {
this.testNumber = 2;
}, 2000);
},
};
</script>
Tester.vue
<template>
<div>
<p>Here is the number straight from the props: {{ testNumber }}</p>
<p>
Here is the number when it goes through computed (does not update):
{{ testNumberComputed }}
</p>
</div>
</template>
<script>
import { computed } from "#vue/composition-api";
export default {
props: {
testNumber: {
type: Number,
required: true,
},
},
setup({ testNumber }) {
return {
testNumberComputed: computed(() => {
return testNumber;
}),
};
},
};
</script>
Here is a working codesandbox:
https://codesandbox.io/s/vue-composition-api-example-forked-l4xpo?file=/src/components/Tester.vue
I know I could use a watcher but I would like to avoid that if I can as it's cleaner the current way I have it
Don't destruct the prop in order to keep its reactivity setup({ testNumber }) :
setup(props) {
return {
testNumberComputed: computed(() => {
return props.testNumber;
}),
};
}

Update State in Vuex?

I am trying to create a user Profile form where you can edit user's information such as name and age. I have 2 routes set up, the /which is for the main user component and the /edit which leads to the user Edit component. I have a user state that i am looping over to output name and age for a user in my User Component. I have added a method in my User component named enterEditMode which on click fetches name and age property of the user selected and then outputs that into the form in my EditMode Component. I am trying to create a method which onClick would update the name or age of the user. So i'd like to click on Sam and on the next page, update his name to Samuel and then click on Update Info button which should update the name Sam to Samuel on my User component page.
But i am having a hard time figuring out how i will do it.
Please check this complete working Example.
This is my Vuex Store:-
state: {
user: [{ name: "Max", age: 29 }, { name: "Sam", age: 28 }],
name: "",
age: null
},
getters: {
getUser: state => state.user,
getName: state => state.user.name,
getAge: state => state.user.age
},
mutations: {
setName(state, payload) {
state.name = payload;
},
setAge(state, payload) {
state.age = payload;
}
}
This is my User Component:-
<template>
<v-container>
<v-flex v-for="(user,index) in getUser" :key="index">
{{ user.name }} {{user.age}}
<v-icon #click="enterEditMode(index)">create</v-icon>
</v-flex>
</v-container>
</template>
<script>
import { mapGetters, mapMutations } from "vuex";
export default {
name: "User",
computed: {
...mapGetters({
getUser: "getUser"
})
},
methods: {
...mapMutations({
setName: "setName",
setAge: "setAge"
}),
enterEditMode(index) {
this.setName(this.getUser[index].name);
this.setAge(this.getUser[index].age);
this.$router.push({ name: "EditMode", params: { index: index } });
}
}
};
</script>
This is my EditMode Component:-
<template>
<v-card>
<v-text-field solo v-model="name"></v-text-field>
<v-text-field solo v-model="age"></v-text-field>
<v-btn>Update Info</v-btn>
</v-card>
</template>
<script>
import { mapGetters } from "vuex";
export default {
computed: {
...mapGetters({
getName: "getName",
getAge: "getAge"
}),
name: {
get() {
return this.getName;
},
set(val) {
return this.$store.commit("setName", val);
}
},
age: {
get() {
return this.getAge;
},
set(val) {
return this.$store.commit("setAge", val);
}
}
},
methods: {
updateInfo() {
this.$router.push('/')
}
}
};
</script>
Thank you for all the help guys. Thank you.
You need to create a mutation in the store to update the user list. For instance to update the selected user name:
Create a updateUserName mutation, and make sure the payload contains both the user index and name to be updated:
mutations: {
updateUserName(state, payload) {
state.user[payload.index].name = payload.name;
}
}
And then in the EditMode.vue file, let the set method of computed name to commit the updateUserName mutation we just created, keep in mind to pass in both the index and name properties:
name: {
get() {
return this.getName;
},
set(val) {
return this.$store.commit("updateUserName", {
name: val,
index: this.index
});
}
}
Here index is another computed property taken from the route parameters for convenience:
index() {
return this.$route.params.index;
},
Check the CodeSandbox working example.

getting data from json in order to fill a datatable

Good day developers ... I'm trying to fill a data table dynamically using information already fetched through a fetch and previously stored in a variable in my vuex instance and now called as computed property in my applets say I have this:
<script>
import { mapActions, mapGetters } from "vuex";
export default {
name: "Games",
data() {
return {
search: "",
headers:[
{text:'Game#', value:'Game#'},
{text:'Players in Game',value:'Players inGame'},
{text:'Permissions',value:'Permissions'},
{text:'Results',value:'Results'},
],
};
},
components: {
},
props: ["gameid"],
methods: {
...mapActions(["fetchingJsonEvents", "joinToGame","logOut", "createGame"]),
},
computed: {
...mapGetters(["getGamesAll", "getUserLogged"]),
getGamesAll(){
return this.$store.getters.getGamesAll.games-------->here is where the json is stored
}
},
created() {
this.fetchingJsonEvents();
}
};
</script>
and my html tag calling this computed and all else is like this:
<v-data-table :search="search" :headers="headers" v-bind:items="getGamesAll">
<template>
<tbody>
<tr v-for="(general, index) in getGamesAll.games" v-bind:key="index">
<td>Game {{general.id}}:</td>
<td>xxxxxxxx</td>
<td>xxxxxxxx</td>
</tbody>
</template>
</v-data-table>
but the table doesn't show any result , it worked with a v-simple-table, but once i try to do it in this way it doesn't....
Any advice .....thanks in advance
this is unnecessary:
getGamesAll(){
return this.$store.getters.getGamesAll.games
}
because you already have it using mapGetters:
...mapGetters(["getGamesAll", "getUserLogged"]),
if your getter getGamesAll have data then this should simply work:
<tr v-for="(general, index) in getGamesAll.games" v-bind:key="index">

v-data-table in component: how to have all items selected by default?

I have a v-data-table in a component, and am using the checkboxes created by select-all to filter information in the component's parent. None of the rows start off selected. I would like all of them to be checked by default.
Things that have not worked:
in the parent's data: setting selectedItems to Array.from(this.$store.state.tableItems) by default (the item in the store isn't defined at that point)
in the mounted or created event in the child: setting selectedItems to Array.from(this.tableItems) (this produces an "avoid mutating a prop directly" error)
I feel like there is probably a way to do this with a computed property?
(There is probably also a more idiomatic way using actions or events or something? I am new to Vue.)
MyComponent.vue
<template>
<v-data-table
:value="selectedItems"
#input="$emit('update:selectedItems', $event)"
:headers="headers"
:items="tableItems"
item-key="id"
select-all
>
<template slot="items" slot-scope="props">
<td>
<v-checkbox v-model="props.selected" hide-details></v-checkbox>
</td>
<td>{{ props.item.id }}</td>
</template>
</v-data-table>
</template>
<script>
export default {
props: {
tableItems: { type: Array, },
selectedItems: { type: Array, },
},
data() {
return {
headers: [
{ text: 'ID', value: 'id', },
],
};
},
};
</script>
Parent.vue
<template>
<MyComponent :tableItems="tableItems" :selectedItems.sync="selectedItems"/>
</template>
<script>
export default {
components: {
MyComponent,
},
data() {
return {
selectedItems: [],
};
},
computed: {
tableItems() {
return this.$store.state.tableItems; // set by an axios request in beforeCreate in App.vue
},
}
};
</script>
Alright, the following did work (I was already using Vuex) but I'm hoping someone else has a more satisfying answer.
store.js
Track the selected items here. The default value of null is so that, when setting the table items after the first axios call finishes, we can set the default.
export default new Vuex.Store({
state: {
tableItems: {},
selectedItems: null,
},
mutations: {
setTableItems(state, payload) {
state.tableItems= payload;
if (state.selectedItems == null) state.selectedItems = payload;
},
setSelectedItems(state, payload) {
state.selectedItems = payload;
},
}
});
MyComponent.vue
What changed here is that I'm using the selected elements to the Vuex store as the value for the data table, and then committing a mutation on every input.
<template>
<v-data-table
:value="selectedItems"
#input="updateSelectedItems"
:headers="headers"
:items="tableItems"
item-key="id"
select-all
>
<template slot="items" slot-scope="props">
<td>
<v-checkbox v-model="props.selected" hide-details></v-checkbox>
</td>
<td>{{ props.item.id }}</td>
</template>
</v-data-table>
</template>
<script>
export default {
props: {
tableItems: { type: Array, },
},
data() {
return {
headers: [
{ text: 'ID', value: 'id', },
],
};
},
methods: {
updateSelectedItems(event) {
this.$store.commit("setSelectedItems", event);
},
},
computed: {
selectedItems() { return this.$store.state.selectedItems; },
}
};
</script>
Parent.vue
This ends up being a lot simpler; less data binding, just referencing the store.
<template>
<MyComponent :tableItems="tableItems"/>
</template>
<script>
export default {
components: {
MyComponent,
},
computed: {
tableItems() { return this.$store.state.tableItems; },
selectedItems() { return this.$store.state.selectedItems; },
}
};
</script>

Vue.js passing store data as prop causes mutation warnings

I'm passing store data (Vuex) as a property of component but it's giving me mutation errors even though I'm not changing the data.
Edit: Codepen illustrating error: https://codesandbox.io/s/v8onvz427l
Input
<template>
<div>
<input type="text" class="form-control" ref="input" />
<div style="padding-top: 5px">
<button #click="create" class="btn btn-primary btn-small">Create</button>
</div>
{{ example }}
</div>
</template>
<script>
import store from "#/store"
export default {
props: {
"example": {
}
},
data() {
return {
store
}
},
methods: {
create() {
store.commit("general_set_creation_name", {name: this.$refs.input.value})
}
}
}
</script>
Modal.vue
<template src="./Modal.html"></template>
<script>
import $ from 'jquery'
import store from '#/store'
export default {
props: {
"id": String,
"height": {
type: String,
default: "auto"
},
"width": {
type: String,
default: "40vw"
},
"position": {
type: String,
default: "absolute"
},
"component": {
default: null
},
"global": {
default: true
}
},
data () {
return {
store: store
}
},
computed: {
body () {
return store.state.General.modal.body
},
props () {
return store.state.General.modal.props
},
title () {
return store.state.General.modal.title
},
},
methods: {
close_modal (event) {
if (event.target === event.currentTarget) {
this.$refs.main.style.display = "none"
}
}
}
}
</script>
<style scoped lang="scss" src="./Modal.scss"></style>
Modal.html
<div
:id="id"
class="main"
ref="main"
#click="close_modal"
>
<div ref="content" class="content" :style="{minHeight: height, minWidth: width, position}">
<div ref="title" class="title" v-if="title">
{{ title }}
</div>
<hr v-if="title" />
<div ref="body" class="body">
<slot></slot>
<component v-if="global" :is="body" v-bind="props"></component>
</div>
</div>
</div>
Changing store data with this line in a third component:
store.commit("general_set_modal", {body: Input, title: "New "+page, props: {example: "example text 2"})
I'm quite sure you should not put a vue component on the state. If you are supposed to do that then I don't think the creators of vuex understand how an event store works.
In the documentation it also says you need to initialize your state with values and you don't do that.
Your sandbox works fine when removing the vue component from the state (state should contain data but vue components are objects with both data and behavior).
index.js in store:
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
modal: {
body: {},
title: "",//det it to something
props: {}
},
creationName: null
},
mutations: {
general_set_creation_name(state, payload) {
state.creationName = payload.name;
},
general_set_modal(state, payload) {
state.modal.title = payload.title;
state.modal.props = payload.props;
console.log("we are fine here");
}
},
strict: process.env.NODE_ENV !== "production"
});
For whatever reason, changing the way I import the class removes the warning
const test = () => import('./Test')
Details:
https://forum.vuejs.org/t/getting-vuex-mutation-error-when-im-only-reading-the-data/27655/11

Categories

Resources