Vue: Watch array data variable doesnt work - vue.js

i'm trying to watch an array declarated in data method (the 'validated' variable). I already have a watcher to an input (legal_name) and it works correctly but the array watcher doesnt give any response. Any idea?
export default {
data() {
return {
legal_name : '',
validated: [],
errors: []
}
},
watch: {
validated() {
console.log('modified')
},
legal_name(value) {
this.eventName();
this.legal_name = value;
this.checkLength(value, 3);
}
},
methods: {
checkLength(value, lengthRequired) {
if(value.length < lengthRequired) {
this.errors[name] = `Debes ingresar al menos ${lengthRequired} caracteres`;
this.validated[name] = false;
return false;
}
this.errors[name] = '';
this.validated[name] = true;
return true;
},
eventName() {
name = event.target.name;
}
}
}

You need to call Vue.set() for arrays, and NOT use indexing such as
foo[3]= 'bar'
Vue DOES recognize some operations, such as splice and push, however.
Read more about it here: https://vuejs.org/2016/02/06/common-gotchas/ and here: https://v2.vuejs.org/v2/guide/list.html#Array-Change-Detection
So for your code, and using the Vue handy helper method $set:
this.validated.$set(name, true);
Why...
Javascript does not offer a hook (overload) for the array index operator ([]), so Vue has no way of intercepting it. This is a limitation of Javascript, not Vue. Here's more on that: How would you overload the [] operator in javascript

Related

How to reference to element from state in foreach (Vuex)?

I am using vuex in my vue application. In store is a declared object:
state: {
list: {
a: false,
b: false,
c: false
}
}
In mutations is mutation that receives arrays in parameters, for example: el: ['a', 'b'].
Those elements in the el array must be set to true in list object in state. I'm using a foreach loop for this:
mutations: {
SET_LIST(state, el) {
el.forEach(element => {
if (state.list.element) {
state.list.element = true;
}
});
}
}
But I am getting an error: error 'element' is defined but never used.
Because element is not used and I don't know how to refer to it correctly.
I searched on the internet and found this solution: state.list[element]. I don't get an error then, but it doesn't work.
Use the bracket notation [] to get the property dynamically :
mutations: {
SET_LIST(state, el) {
el.forEach(element => {
if (Object.keys(state.list).includes(element)) {
state.list[element] = true;
}
});
}
}

Vue.js 2: action upon state variable change

I am using a simple state manager (NOT vuex) as detailed in the official docs. Simplified, it looks like this:
export const stateholder = {
state: {
teams: [{id: 1, name:'Dallas Cowboys'}, {id: 2, name:'Chicago Bears'}, {id: 3, name:'Philadelphia Eagles'}, {id:4, name:'L.A. Rams'}],
selectedTeam: 2,
players: []
}
getPlayerList: async function() {
await axios.get(`http://www.someapi.com/api/teams/${selectedTeam}/players`)
.then((response) => {
this.state.players = response.data;
})
}
}
How can I (reactively, not via the onChange event of an HTML element) ensure players gets updated (via getPlayerList) every time the selectedTeam changes?
Any examples of simple state that goes a little further than the official docs? Thank you.
Internally, Vue uses Object.defineProperty to convert properties to getter/setter pairs to make them reactive. This is mentioned in the docs at https://v2.vuejs.org/v2/guide/reactivity.html#How-Changes-Are-Tracked:
When you pass a plain JavaScript object to a Vue instance as its data
option, Vue will walk through all of its properties and convert them
to getter/setters using Object.defineProperty.
You can see how this is set up in the Vue source code here: https://github.com/vuejs/vue/blob/79cabadeace0e01fb63aa9f220f41193c0ca93af/src/core/observer/index.js#L134.
You could do the same to trigger getPlayerList when selectedTeam changes:
function defineReactive(obj, key) {
let val = obj[key]
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
return val;
},
set: function reactiveSetter(newVal) {
val = newVal;
stateholder.getPlayerList();
}
})
}
defineReactive(stateholder.state, 'selectedTeam');
Or you could set it up implicitly using an internal property:
const stateholder = {
state: {
teams: [/* ... */],
_selectedTeam: 2,
get selectedTeam() {
return this._selectedTeam;
},
set selectedTeam(val) {
this._selectedTeam = val;
stateholder.getPlayerList();
},
players: []
},
getPlayerList: async function() {
/* ... */
},
};
Your question is also similar to Call a function when a property gets set on an object, and you may find some more information there.
You could use v-on:change or #change for short to trigger getPlayerList.
Here a fiddle, simulating the request with setTimeout.

How to update an object in 'state' with react redux?

In my reducer, suppose originally I have this state:
{
"loading": false,
"data": {
"-L1LwSwW97KkwdSnYvsc": {
"likeCount": 10,
"liked": false, // I want to update this property
"commentCount": 5
},
"-L1EY2_fqzn7sM1Mbf_F": {
"likeCount": 8,
"liked": true,
"commentCount": 22
}
}
}
Now, I want to update liked property inside -L1LwSwW97KkwdSnYvsc object, which is inside data object and make it true. This is what I've been trying, but apparently, it's wrong, because after I've updated the state, the componentWillReceiveProps function inside a component that listens to the state change does not get triggered:
var { data } = state;
data['-L1LwSwW97KkwdSnYvsc'].liked = !data['-L1LwSwW97KkwdSnYvsc'].liked;
return { ...state, data };
Could you please specify why it's wrong and how I should change it to make it work?
You're mutating state! When you destructure:
var { data } = state;
It's the same as:
var data = state.data;
So when you do:
data[…].liked = !data[…].liked
You're still modifying state.data which is in turn mutating state. That's never good - use some nested spread syntax:
return {
...state,
data: {
...state.data,
'-L1LwSwW97KkwdSnYvsc': {
...state.data['-L1LwSwW97KkwdSnYvsc'],
liked: !state.data['-L1LwSwW97KkwdSnYvsc'].liked
}
}
};
Using spread operator is good until you start working with deeply nested state and/or arrays(remember spread operator does a shallow copy only).
I would rather recommend you starting working with immutability-helper instead. It is a React recommendation and it will let your code more readable and bug free.
Example:
import update from "immutability-helper";
(...)
const toggleLike = !state.data["-L1LwSwW97KkwdSnYvsc"].liked
return update(state, {
data: {
"-L1LwSwW97KkwdSnYvsc": {
like: {
$set: toggleLike
}
}
}
})

How to populate empty array with a method in Vue

I'm trying to populate an empty array with a declared array variable in a computed function. I tried this but with no luck:
data: {
hashtags: []
},
computed: {
filteredHashtags () {
var defaultHashtags = [ '#hr', '#acc', '#sales' ];
var fHashtags =
_.chain( messages )
.pluck( 'hashtags' )
.flatten()
.map(
function ( tag ) {
return tag && tag.trim() ? '#' + tag : null; })
.filter( Boolean )
.value();
fHashtags = _.union( fHashtags, defaultHashtags );
return data.hashtags = fHashtags;
}
}
also, is there a better method to approach this?
A computed property isn't really a good use case for this, because the computed value has to be referenced in order for it to be called. Instead, just make it a method and call it the method when your Vue is created.
data: {
hashtags: []
},
methods: {
filterHashtags() {
// commented out stuff
// set the data property with the filtered values
this.hashtags = fHashtags;
}
},
created(){
this.filterHashtags();
}

Two-way filter updating on the fly | Vue.js

How one can do custom two-way filter for model, updating on the fly in Vue.js.
The following code example from docs works on input blur. But I need it work on keypress.
Vue.filter('currencyDisplay', {
read: function(val) {
return '$'+val.toFixed(2)
},
write: function(val, oldVal) {
var number = +val.replace(/[^\d.]/g, '')
return isNaN(number) ? 0 : parseFloat(number.toFixed(2))
}
})
Many thanks in advance for any help!
You can apply a filter to a Vue data property by creating a computed property with a get and set method that fire the read and write methods of the filter, respectively:
data() {
return {
foo: 0,
}
},
computed: {
filteredFoo: {
get() {
return Vue.filter('currencyDisplay').read(this.foo);
},
set(value) {
this.foo = Vue.filter('currencyDisplay').write(value);
}
}
}
Here's a working fiddle.