How in methods function call variable from vuex store? - javascript

In my Vue.js component, I have v-select element. When the user selects some value in that widget I call toDo function which is defined in methods block. As you can see in that function I want to know the value of the getter called filters. Unfortunately, it returns me undefined. At the same time in DevTools of Vue I notice that this getter has value. How in function correctly take the value of the getter?
QUESTION:
I use filters in the template and they are displayed on the interface without any problem. But in toDo function, the same filters return undefined result. I want to understand this strange behavior.
<div
v-for="filter in filters"
:key="filter.id">
<v-checkbox
v-for="(item, index) in filter.values"
:label="filter.description_values[index]"
:value="item"
:key="item"
v-model="filter.selected_values"
hide-details>
</v-checkbox>
<v-select
v-if="filter.widget==='single_dropdown'"
v-model="filter.selected_values"
:items="filter.filter_values"
label="Select ..."
dense
solo
#change="toDo">
</v-select>
</div>
***
computed: {
...mapGetters('store', [
'filters'
]),
},
methods: {
toDo: async (value) {
await console.log(this.filters) // result: undefined
}
}
***
Vuex Storage:
import { api } from '../../services/api'
export default {
namespaced: true,
state: {
filters: null
},
getters: {
filters: state => state.filters
},
mutations: {
setStateValue: (state, {key, value}) => {
state[key] = value
}
},
actions: {
getFilters: async (context) => {
await api.get('/api/filters').then((response) => {
context.commit('setStateValue', {
key: 'filters',
value: response.data
})
})
}
}
}

In your mapGetters computed, call all getters inside a one Array:
computed: {
...mapGetters(['store', 'filters']),
},

The filters getter is at the root of your store; it's not inside a module. You can access it without namespacing:
computed: {
...mapGetters(['filters']),
},

At first - if Vuex code you provided is your main store (not a part of a module) you should remove namespaced: true, - it is used solely for vuex modules.
and if Vuex code you provided is not part of a Vuex module you should simply map getters this way:
computed: {
...mapGetters(['filters']),
},
More info - https://vuex.vuejs.org/guide/modules.html#binding-helpers-with-namespace

Related

Vue JS - Rendering getter before template

I'm getting TypeError "Cannot read property 'email' of undefined" because it seems that the template is rendering before the getter returns the value. The value is indeed undefined as it is initialised as undefined in the store. But after the template renders, that value does return something. Is there anyway I can have my getter render after the template?
my code:
<template>
<div>
<Success :title="'title name'"
:subtitle="`your email is ${schoolDetails.email}.`"
:button-text="'button text'"
:button-link="ROUTE_NAMES_HK_ADMIN.SCHOOL_DETAILS"/>
</div>
</template>
<script>
import {ROUTE_NAMES_HK_ADMIN} from "#/router/modules/hkAdmin";
import Success from "#/components/partials/Success";
import {GET_SCHOOL_BY_ID} from "#/store/manager/actions";
export default {
name: "SchoolCreateSuccess",
components: {Success},
data: () => ({
ROUTE_NAMES_HK_ADMIN
}),
computed: {
schoolDetails: function () {
return this.$store.getters.getSelectedSchool;
},
},
methods: {
getSchoolDetails: function (schoolId) {
this.$store.dispatch(GET_SCHOOL_BY_ID, schoolId);
}
},
created() {
this.getSchoolDetails(this.$route.params.id);
}
}
How about initializing the schoolDetails variable with dummy value to fulfil the error?
Then maybe you can use watch instead of computed to align tracking the schoolDetails variable with the stored data.
So, maybe something like this:
data: () => ({
ROUTE_NAMES_HK_ADMIN,
schoolDetails: {email: ''}
}),
// note: 'watch' track changes (no changes == the function will not be called)
watch: {
// watch the state, not the getter
'$store.state.selectedSchool': () => {
this.schoolDetails = this.$store.getters.getSelectedSchool;
return;
}
}

How to instantaneously update changes committed to vuex store in the ui of another component?

How to instantaneously update changes committed to vuex store in the ui of another component?
following is the vuex store module of a component
const { property } = require("lodash")
export default {
state: {
data: [
{id: 1, name: 'Angular', status: 'active'},
{id: 2, name: 'React', status: 'active'},
{id: 3, name: 'Vue', status: 'inactive'},
]
},
mutations: {
UPDATE_ITEM(state, payload) {
const item = state.data.find(item => _.isEqual(item, payload.item))
Object.assign(item, payload.status);
},
},
actions: {
updateItem({ commit }, payload) {
commit("UPDATE_ITEM", payload);
},
},
getters: {
getData: (state) => state.data,
getMappedStatus: (state) => state.data.map(data => data.status)
},
};
this is how I get mapped status in component 1
computed: {
getMappedStatus() {
return this.$store.geters.getMappedStatus
}
}
and inside the ui of component 1
<ul>
<li v-for="item in getMappedStatus>{{item}} </li>
</ul>
from another component 2 I'm updating the changes as the user inputs the status:
onStatusChanges(item, status) {
this.$store.dispatch("updateItem", {
item, status
});
},
But the problem is that even though the state is getting updated the ui is not getting updated.But
(inside component 1)
I think it is because of the computed property. ... not sure.
How can I implement something like and observable or something reactive so that the getMappedStatus computed property in component 1 will get updated automatically as action is dispatched from component 2.
NOTE Both components come under the same vuex store module
If this was in angular/ngrx I would subscribe to the selectors inside the component 1 and I would get instantaneously the changes. even If I subscribe to the event inside the onInit() method or constructor() and do a console.log() the changes from the other component will be reflected instantaneously.
But this is not happening with vuex.
How can I achieve that:?
or is there a way to trigger the updation of the getMappedStatus computed property inside component 1 as soon as changes occurs from component 2:
computed: {
getMappedStatus() {
return this.$store.geters.getMappedStatus
}
}
so that the ui of component 1 updates instantaneously.
We can think of getters as computed properties for stores. Like computed properties, a getter's result is cached based on its dependencies, and will only re-evaluate when some of its dependencies have changed so that simply means if component1 mutates the state in vuex store and component2 uses that property from the store it will automatically update in all components using it.
Online IDE - Live Demo Here
Vuex Store
const store = new Vuex.Store({
state: {
name: 'mex'
},
mutations: {
mutateName(state, value) {
state.name = value;
}
},
actions: {
updateName(context, payload) {
context.commit('mutateName', payload.name);
}
},
getters: {
getName(state) {
return state.name;
}
}
});
The changeName method when fired will dispatch updateName action which will update the name and all the components will update accordingly because it's reactive.
Then In Any Component
computed:{
name() {
return this.$store.getters.getName
}
},
methods: {
changeName: function () {
this.$store.dispatch('updateName', { name: 'mexxxxx'});
}
}
You need to return something from getMappedStatus
computed: {
getMappedStatus() {
return this.$store.geters.getMappedStatus
}
}
...without a return statement the this.$store.geters.getMappedStatus is just an expression and your computed property always returns undefined (because JS functions without return always return undefined)

v-model with dynamic input from computed

So I had a use case when I need to generate the dynamic form in a website. I'm using v-for to iterate on my form and v-model on computed properties, on Vue docs it is stated that using get() or set() are the way to go, but I got an error message
[vuex] do not mutate vuex store state outside mutation handlers.
Here my code :
store.js
function dynamic() {
return {
ini: 12
};
}
export const state = () => ({
message: []
});
export const mutations = {
setMessage(state, value) {
console.log(value);
state.message = value;
},
setDynamic(state, value) {
let arr = [];
for (let i = 0; i < 2; i++) {
arr.push(dynamic());
}
state.message = arr;
}
};
component.vue
<template>
<section>
<div v-for="(item, index) in message" :key="index">
<input placeholder="Hallo V-Model" v-model="message[index].ini">
</div>
</section>
</template>
<script>
import { mapMutations } from "vuex";
export default {
computed: {
message: {
get() {
return this.$store.state.messages.message;
},
set(value) {
this.$store.commit("messages/setMessage", value);
}
}
},
methods: {
...mapMutations({
dynamic: "messages/setDynamic"
})
},
beforeMount() {
this.dynamic();
}
};
</script>
More interactive one can be found here at my codesandbox do you had any idea why it throw error? and how to fix this issue?
Update
Manage to fix it with set 'strich mode' to false, but still, I don't know where I mutate the state outside of mutation. Answer still needed
I use vuex-maps-fields as a shortcut, they provide higher level get() and set() whitin vuejs so you can use v-model with them
I mutated the object on messages so I need to clone it before applying new value. Here how the library does that so It wont mutate your state outside mutations https://github.com/maoberlehner/vuex-map-fields/blob/8ce9a2751be7996214c0c68c25afa2f2ef5b7446/src/index.js#L61

Vue component doesn't update when state in store is updated

I'm trying to do a simple todo list in Vue but I want to abstract everything out and use a dummy REST API so I can start to get used to production-level projects in Vue and it's all making my head spin. GET, PUT, and POST requests seem to be working, but I can't figure out why the list of todos doesn't update automatically when I do a successful POST request to the back end.
I've got a TodoList component that loops through a todosFiltered() computed property to show the todos. The computed property refers back to the getter todosFiltered in the Vuex store. I also use the created() lifecycle hook here to dispatch an action in the store that makes the initial GET request and then populates an array called todos in the store when the page is first loaded. The getter todosFiltered in the store returns state.todos, so I assumed that when my component re-renders, it would have the new todos array from the state grabbed from todosFiltered, only that's not happening. What am I missing here? Any advice would be greatly appreciated.
TodoList.vue
(I know I'll have to work out a solution for the ids, it's on my list :p)
<template>
<div class="container">
<input v-model="newTodo" type="text" placeholder="What must be done?" class="todo-input" #keyup.enter="addTodo">
<transition-group name="fade" enter-active-class="animated zoomIn" leave-active-class="animated zoomOut">
<todo-item v-for="todo in todosFiltered" :key="todo.id" :checkAll="!anyRemaining" :todo="todo"></todo-item>
</transition-group>
<div class="extra-container">
<todos-filtered></todos-filtered>
</div>
</div>
</template>
<script>
import TodosFiltered from './TodosFiltered'
import TodoItem from './TodoItem'
export default {
name: 'todolist',
components: {
TodosFiltered,
TodoItem
},
data() {
return {
beforeEditCache: '',
newTodo: '',
idForTodo: 10,
}
},
// Methods
methods: {
addTodo() {
if (this.newTodo.trim().length == 0) {
return
}
this.$store.dispatch('addTodo', {
id: this.idForTodo,
title: this.newTodo,
completed: false
})
this.newTodo = ''
this.idForTodo++
}
},
computed: {
todosFiltered() {
return this.$store.getters.todosFiltered
},
},
created() {
this.$store.dispatch('loadTodos')
},
}
</script>
store.js
export const store = new Vuex.Store({
state: {
filter: 'all',
todos: []
},
getters: {
todosFiltered(state) {
if (state.filter == 'all') {
return state.todos
} else if (state.filter == 'active') {
return state.todos.filter(todo => !todo.completed)
} else if (state.filter == 'completed') {
return state.todos.filter(todo => todo.completed)
}
return state.todos
},
showClearCompleted(state) {
return state.todos.filter(todo => todo.completed).length > 0
}
},
mutations: {
addTodo(state, todo) {
state.todos.push(todo)
},
setTodos(state, todos) {
state.todos = todos
},
},
actions: {
loadTodos(context) {
axios.get('http://localhost:3000/todos')
.then(r => r.data)
.then(todos => {
context.commit('setTodos', todos)
})
},
updateTodo(context, todo) {
axios.put('http://localhost:3000/todos/' + todo.id, {
"id": todo.id,
"title": todo.title,
"completed": todo.completed
})
},
addTodo(context, todo) {
axios.post('http://localhost:3000/todos', {
"id": todo.id,
"title": todo.title,
"completed": todo.completed
})
.then(todo => {
context.commit('addTodo', todo)
})
},
}
})
EDIT: Here's what's going on in Vue Dev Tools when I add a todo -- todos in the store's state gets updated immediately, and the todosFiltered computed property in the TodoList component ALSO reflects that -- but the new todo doesn't appear in the list! Strange.
A way to solve this can be to create what I like to call a refresh() method.
Basically, you will have a local list of todos in your data() method, the refresh() method will load all the todos from the store into the local todos list, every time you do an action, such as creating, deleting, or updating, you would call the refresh method to re-load the list for you.
So, in your TodoList.vue:
<template>
<todo-item v-for="todo in todosFiltered" :key="todo.id"></todo-item>
</template>
<script>
export default {
data() {
return {
// Where we store the local list of Todos
// so the component will react when we do something to it
todosFiltered: []
}
},
methods {
refresh() {
// Get the todo list from the store.
// which in turn will trigger a change event so the component
// react to what we did.
this.todosFiltered = this.$store.getters.todosFiltered;
},
addTodo() {
this.$store.dispatch('addTodo').then(() => {
// Refresh the list after adding a new Todo
this.refresh();
})
},
updateTodo() {
this.$store.dispatch('updateTodo').then(() => {
// Refresh the list after updating a Todo
this.refresh();
})
},
deleteTodo() {
this.$store.dispatch('deleteTodo').then(() => {
// Refresh the list after deleting a Todo
this.refresh();
})
}
},
created() {
this.$store.dispatch('loadTodos').then( () => {
// Refresh the list when first loaded after the Todos been saved in Vuex
this.refresh();
})
}
}
</script>
Don't actually delete what you already have and replace it with
this, just apply what's here to your code.
The problem is using a $store.getter on the v-for loop.
Try the following:
Set your computed to
todos() {
return this.$store.todos;
}
Change your v-for loop to use todo in todos
Add a v-if condition to the loop like v-if="filtered(todo)"
Create a new method called filtered (or whatever you prefer), and add your "filteredTodos" logic there, returning true/false as needed
If you need to share this code, you can always use a mixin and share it between your components
Hope this works for you

Setting props for components using v-for in Vue JS

I have a PhoneCard.vue component that I'm trying to pass props to.
<template>
<div class='phone-number-card'>
<div class='number-card-header'>
<h4 class="number-card-header-text">{{ cardData.phone_number }}</h4>
<span class="number-card-subheader">
{{ cardData.username }}
</span>
</div>
</div>
</template>
<script>
export default {
props: ['userData'],
components: {
},
data() {
return {
cardData: {}
}
},
methods: {
setCardData() {
this.cardData = this.userData;
console.log(this.cardData);
}
},
watch: {
userData() {
this.setCardData();
}
}
}
The component receives a property of userData, which is then being set to the cardData property of the component.
I have another Vue.js component that I'm using as a page. On this page I'm making an AJAX call to an api to get a list of numbers and users.
import PhoneCard from './../../global/PhoneCard.vue';
export default {
components: {
'phone-card': PhoneCard
},
data() {
return {
phoneNumbers: [],
}
},
methods: {
fetchActiveNumbers() {
console.log('fetch active num');
axios.get('/api').then(res => {
this.phoneNumbers = res.data;
}).catch(err => {
console.log(err.response.data);
})
}
},
mounted() {
this.fetchActiveNumbers();
}
}
Then once I've set the response data from the ajax call equal to the phoneNumbers property.
After this comes the issue, I try to iterate through each number in the phoneNumber array and bind the value for the current number being iterated through to the Card's component, like so:
<phone-card v-for="number in phoneNumbers" :user-data="number"></phone-card>
However this leads to errors in dev tools such as property username is undefined, error rendering component, cannot read property split of undefined.
I've tried other ways to do this but they all seem to cause the same error. any ideas on how to properly bind props of a component to the current iteration object of a vue-for loop?
Try
export default {
props: ['userData'],
data() {
return {
cardData: this.userData
}
}
}
Answered my own question, after some tinkering.
instead of calling a function to set the data in the watch function, all I had to do was this to get it working.
mounted() {
this.cardData = this.userData;
}
weird, I've used the watch method to listen for changes to the props of components before and it's worked flawlessly but I guess there's something different going on here. Any insight on what's different or why it works like this would be cool!

Categories

Resources