v-model with dynamic input from computed - javascript

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

Related

is there a way to sync vue data values with global store state values?

I want to update my data values with values in the store.js, how it is possible? below code gave me blank page error.
App.vue
data() {
return {
storeState: store.state,
Counter: this.storeState.Counter,
}
}
store.js
export const store = {
state: {
Counter: 1,
}
CounterUpdater(value) {
this.state.Counter = (value);
},
}
You can't refer to a data property (storeState) inside the data option that way, and you don't need it anyway. You should use computeds to sync component values with Vuex values. Remove both data values:
computed: {
Counter() {
return this.$store.state.Counter;
}
}
Or use mapState:
import { mapState } from 'vuex'
computed: {
Counter() {
...mapState(['Counter'])
}
}
Also make sure your store mutation is inside mutations and using the proper syntax:
state: {
Counter: 1
},
mutations: {
CounterUpdater(state, value) {
state.Counter = value;
}
}
It's also recommended to name your variables according to camelCase convention (i.e. means lowercase counter in your code)

How to handle multiple array filters with Vue JS?

Not sure when to use computed properties vs watch to display my data. I'm building an app with the PokeAPI, and I want to toggle between Type and Generation to display the pokemon. So far I have a JS file that stores all the pokemon in an array:
//pokeData.js
import axios from 'axios'
const allPokes = [];
export default{
getPokemon(){
if(allPokes.length === 0){
for(let i=1; i<=809; i++){
axios.get(`https://pokeapi.co/api/v2/pokemon/${i}`)
.then(response => {
allPokes.push(response.data);
allPokes.sort((a, b) => a.id - b.id);
});
}
}
return allPokes
}
}
I don't want to repeatedly call 809 Objects from an API, so I call them in the mounted() in my Vue, and want to filter them from there:
//Container.vue
//takes two props, 'currentType' and 'currentGen', to use to filter the pokemon
<template>
<div
v-for="(pokemon, index) in allPokemon"
:key="index">
<h2>{{ pokemon.name }}</h2>
</div>
</template>
<script>
import Pokemon from '../pokeData'
export default {
props: ['currentType', 'currentGen'],
data(){
return{
allPokemon: [],
}
},
mounted(){
this.allPokemon = Pokemon.getPokemon();
},
watch: {
currentType: function(newType){
const typePokes = this.allPokemon.filter(pokemon => {
if(pokemon.types[0].type.name == newType){
return true
}
this.allPokemon = typePokes
});
I know this is wrong, but I don't know how to fix it. I know you can use List Rendering as suggested in the official documents, but it doesn't say how to use it for multiple filters. https://v2.vuejs.org/v2/guide/list.html#Replacing-an-Array
Any advice is welcome: how to better cache the initial API call; to use watch or computed...
A computed prop is what you need in this situation:
computed: {
filteredPokemons () {
if (this.currentType) {
return this.allPokemon.filter(pokemon => pokemon.types[0].type.name == this.currentType)
}
if (this.currentGen) {
// I assumed that a prop is named 'gen', just replace with an actual prop
return this.allPokemon.filter(pokemon => pokemon.types[0].gen.name == this.currentGen)
}
// you didn't mentioned what to show when both currentType and currentGen is empty
// I assumed it should be an empty array
return []
}
}

How in methods function call variable from vuex store?

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

Error in render function: “TypeError: Cannot read property of undefined”, using Vuex and Vue Router

I have been dealing with an issue using Vue, Vuex and Vue-Router. I'm building a flash cards app, fetching all the cards on main app creation, then using a Vuex getter to get each card by its id which is passed as a route parameter.
Relevant bits:
App.vue
export default {
components: {
'app-header': header,
},
data() {
return {
}
},
created() {
this.$store.dispatch('getAllCards');
}
}
The dispatch('getAllCards') is just pulling all the cards from the DB and committing to Vuex store.js.
Now I set up a getter:
getters: {
cardById: (state) => (id) => {
return state.allCards.find((card) => card._id === id);
}
}
Here is Card.vue:
<template>
<div>
<br>
<div v-if="flipped" class="container">
<div class="box">
<pre v-if="card.code"><code class="preserve-ws">{{card.back}}</code></pre>
<p class="preserve-ws center-vertical" v-else>{{card.back}}</p>
</div>
</div>
<div v-else class="container">
<div class="box">
<h1 class="title has-text-centered center-vertical is-2">{{card.front}}</h1>
</div>
</div>
</template>
<script>
export default {
data() {
return {
card: {},
flipped: false,
general_card: false,
code_card: true,
random_card: false
}
},
computed: {
},
methods: {
},
created() {
this.card = this.$store.getters.cardById(this.$route.params.id);
}
}
</script>
I am getting the TypeError referenced in the title. My understanding is that the created() hook happens after data() has been set up, so then I can assign {card} using the getter. Unfortunately this displays nothing...
If I assign card() as a computed property:
computed: {
card() {
return this.$store.getters.cardById(this.$route.params.id);
}
}
The card shows, but I still get that error in console. Any idea why? I looked at this and attempted that solution, but to no avail.
The question don't have all the premise to get a correct answer.
(We don't know the mutations, the state, neither the actions and we have no clues about app-header component)
So we have to admit some hypothesis :
state.allCards is an empty Array when you mount the component
action getAllCards is an async function to retrieve or set state.allCards with a mutation
cardById's getters, return a function, so vuex can't apply reactive on a function. So if the state change, the getter won't be trigger correctly.
To correct this, use computed as you mention here.
But it don't fix the undefined error, that from my point of view, is because the getters return undefined on mount.
getters: {
cardById: (state) => (id) => {
var card = state.allCards.find((card) => card._id === id);
if(card) {
return card;
}
// on undefined return a default value
return {
front:'default value'
};
}
}
You can see the implementation on this jsfiddle with no error on console.
Or you can have a loading state on undefined value for your card component.
If my hypothesis is wrong please provide a jsfiddle to help you.
What you need is a derived state based on store state which is to return a filtered card based on a card id. This id is received to your component via the route params. So its better you use a computed property instead of passing arguments to the store getters
Instead of initializing card in data property make card a computed property like this:
computed:{
card(){
return this.$store.state.allCards.find((card) => card._id === this.$route.params.id);
}
}
Note this
If a component needs derived store state based on its own state(in your case rourte params), it should define a local computed property
I tried everyone else's solutions and they did not work. But I got it to work finally. Here is what worked for me:
I ended up including a top-level:
<div v-if="!card"> Loading card... </div>
<div v-else> Rest of card template </div>
That seems to have silenced the error. Also, the card lives as a computed property:
card() {
return this.$store.getters.cardById(this.$route.params.id);
}
I think it was throw error in this step
getters: {
cardById: (state) => (id) => {
return state.allCards.find((card) => card._id === id);
}
}
in this step, it can not find _id of cars, because allcards was null;
and then you use computed instead, when allcards has been change, it will get again;
you can change code like this
getters: {
cardById: (state) => (id) => {
return state.allCards.find((card) => card && card._id === id);
}
}
$route.params.id must be string, so what is the type of card._id? It seems each card._id is number, I think.

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