Is this the best way to handle props that could be empty? - vue.js

I have an scenario where I pass an array as a prop to a component. I then filter out the object I need based on an ID I passed as another prop. I do this with a computed property.
for example
people[
{id: '1', name: 'Frank', age: '33'},
{id: '2', name: 'Bethany', age: '22'},
{id: '3', name: 'Roscoe', age: '44'},
]
<my-component :person-id="id" :peopleArray="people[]"></my-component>
I pre-fill the component data with empty strings in case the data I'm calling is empty
data: function(){
personName: '',
personAge: ''
},
and then use the computed property to to populate that data
computed: {
getActivePersonById: function(){
return this.people.find(x => x.id === '1')
},
getActivePersonName: function(){
return this.personName = this.getActivePersonById.name
},
getActivePersonId: function(){
return this.personName = this.getActivePersonById.age
},
}
I then use the values in the HTML like so
(I'm resorting to :value because v-model was not updating from the computed property)
<label for="person_name">Name</label>
<input type="text" name="person_name :value="personName">
<label for="person_age">Age</label>
<input type="text" name="person_age :value="personAge">
Finally I need to submit any updated data by the user, I can try grab the values from data or get the values with a js query. Please let me know if there is a better way of doing this. I'd love to use the props data directly but I need to filter it by ID first.

Computed properties are by default getters only i.e they just return a value. To use a computed property as a v-model you should make use of computed setters.
computed: {
getActivePersonById: function(){
return this.people.find(x => x.id === '1')
},
getActivePersonName: {
get(){
return this.getActivePersonById.name
},
set(newVal){
this.personName = newVal
}
},
getActivePersonAge: {
get(){
return this.getActivePersonById.age
},
set(newVal){
this.personAge = newVal
}
}
}
Then use these computed properties as v-model for your inputs
<label for="person_name">Name</label>
<input type="text" name="person_name v-model="getActivePersonName">
<label for="person_age">Age</label>
<input type="text" name="person_age v-model="getActivePersonAge">
or else
make use of the created () lifecycle hook to initialize your data properties
created(){
this.personName = this.getActivePersonName
this.personAge = this.getActivePersonAge
}
note: in your computed properties just return values do not assign like you are doing

Just to add to the accepted answer. In my scenario both the computed setters and created solutions did not work.
However in a test with simpler data they did work. This was because I was initializing the component before the ID I was passing was available. I simply changed the component to only show when the ID was passed to it via v-if.
Now everything works fine. It was just a case of understanding how and when the component was initialized.

Related

Vue.js dynamic binding v-model and value

I use django-rest-framework + vue.js
My goal is to make a Form to edit user-profile.
Here is what i have:
<input type="email" v-model="userEdit.email">
<input type="text" v-model="userEdit.location">
<input type="text" v-model="userEdit.bio">
my inputs are bounded to data object "editUser"
data() {
return {
'editUser': {
email: '',
location: '',
bio: '',
image: '',
},
}
},
so now i can send this object to the server and change user-profile information.
sendChanges() {
const fd = new FormData();
fd.append('image', this.editUser.image, this.editUser.image.name)
fd.append('email', this.editUser.email)
fd.append('location', this.editUser.location)
fd.append('bio', this.editUser.bio)
this.axios.put(userDetailURL + this.routeUser, fd)
.then(response => {
console.log(response)
})
.catch(error => {
console.log(error.response)
})
},
this form works and updates info, but there is a thing i dont like:
The input fields are always empty, and user needs to fill all of them before can press save button.
even if the user wants to change only "location" he must fill other inputs which are empty.
adding dynamic :value="userDetail.email" to the input -- no work.
is there any other way to add current value to input field and still have v-model?
current data is here:
computed: {
userDetail() {
return this.$store.getters.userDetail;
},
},
The problem is that you are binding the values in data to the form and those values are initially empty.
The cleanest and easiest solution I can think of right now is updating the initial data in mounted lifecycle hook:
mounted () {
// Use Object.clone to prevent modifying the userDetail object directly
this.editUser = Object.clone(this.$store.getters.userDetail)
}
There are other solutions, though. You could use a computed setter whose getter defaults to whatever is in the store but can be overridden when set.

How to use parent's mixin as a child model

Im trying to use a parent's mixin as a child model, but I couldn't make it work as the mixin isn't resolved on the 'data' definition.
Here is a fiddle:
<div id="vue-instance">
<div>
USER: {{user}}
<br/>
EMAIL: {{email}}
</div>
<input-list :field="field" v-for="field in fields"/>
</div>
js:
Vue.component('input-list', {
name: 'InputList',
props: ['field'],
template: '<div>{{field.id}}: <input type="text" v-model="field.model"/></div>'
})
var userData = {
data() {
return {
user: 'foo',
email: 'foo#barbar'
}
}
}
var vm = new Vue({
el: '#vue-instance',
mixins: [userData],
data() {
return {
fields: [
{
id: "UserName",
model: this.user
},
{
id: "Email",
model: this.email
}
]
}
}
});
https://jsfiddle.net/rafalages/rp7bu2qt/9/
The expected result would be update parent mixin value in the child input.
This is not an issue with the mixin, but rather the overall design, if you we to use data instead of mixin, you'd see the same behaviour.
This will not work they way you intend it to.
Two things about Vue worth re-iterating:
You pass props down and emit events up
Mutating a prop will not update the parent
more reading here:
https://v2.vuejs.org/v2/guide/components-props.html#One-Way-Data-Flow
There is a note at the bottom
Note that objects and arrays in JavaScript are passed by reference, so if the prop is an array or object, mutating the object or array itself inside the child component will affect parent state.
But my recommendation is to think of this as a side-effect rather than a feature. You should not rely on the child from updating the value. The only reliable way (outside of other state management) is to pass a function.
Another issue is that referencing this in data during creation will not work
data: {
fields: [
{
id: "UserName",
model: this.user
},
{
id: "Email",
model: this.email
}
]
}
This will not use the data from this.user and will be set to null. You can use the mounted life-cycle function to set these like this:
mounted() {
this.fields = [
{
id: "UserName",
model: this.user
},
{
id: "Email",
model: this.email
},
]
}
which will set it to use the initial values. But referencing an object like this inside another object will also serve to create new bindings, essentially cutting the reactivity. This means that you'll use the values from those objects, but changes to this.fields[1].model will not update this.email
here is a semi-working fiddle that attempts to use your current design
https://jsfiddle.net/rp7bu2qt/113/
notice that...
the binding is using v-model, which is shorthand for binding a prop and an emit/update to a value
you see an error
You are binding v-model directly to a v-for iteration alias. This will not be able to modify the v-for source array because writing to the alias is like modifying a function local variable. Consider using an array of objects and use v-model on an object property instead.
changes to the input fields do not change the original data, just the content of fields[n].model

How to initialize value based on ajax response?

I need to create a checkbox that will be checked/unchecked depending on the value of a parameter coming from the database.
I'm not able to load that value when I'm rendering the page, so the idea is: render the page, "tell" the checkbox to "ask" the server what is the current value of the parameter and then check/uncheck the checkbox depending on the response. Then, if the user checks/unchecks the checkbox, make a new Ajax request to update the value in the database.
I wrote some code (I'm new in Vuejs, so for sure I'm doing something wrong):
var vm = new Vue({
el: '#root',
computed: {
checked() {
return this.initialize()
},
value() {
return this.checked
}
},
watch: {
checked() {
alert('watcher')
this.update();
}
},
methods: {
initialize(){
// Just pretending an initial value
var randomBoolean = Math.random() >= 0.5;
alert('Ajax request here to initialize it as ' + (randomBoolean ? 'checked' : 'unchecked'));
return randomBoolean;
},
update(){
alert('ajax request here to set it to ' + this.value)
}
}
});
You can check and run the code here: https://jsfiddle.net/hyn9Lcv2/
Basically it works to initialize the checkbox, but then it fails to update. If you check the console, there is this error:
[Vue warn]: Computed property "checked" was assigned to but it has no setter.
First have you thought of using the created() hook from the vue instance instead of watcher?
It's recommended and will execute the code as soon as the component is created.
From the doc:
new Vue({
data: {
a: 1
},
created: function () {
//Ajax call:
//onsuccess(response){
this.a = reponse.data.a
}
}
})
in the created hook you can do your ajax call, (axios is good library for that, worth checking it out: https://github.com/axios/axios ).
Then from your ajax response you can link the desired value to your checkbox by assigning it to a variable in the data object of the instance (in our case 'a')
Then bind it to your checkbox with the v-model like this:
<input
type="checkbox"
v-model="a">
I recommend to check the vue doc for more info on biding: https://v2.vuejs.org/v2/guide/forms.html#Checkbox-1
Hope it helps.
Just add bind click event
<div id="root">
<input id="check" type="checkbox" name="active" v-model="checked" #click="update">
<label for="check">Click me</label>
</div>
You need to fetch the database value when the component is created or mounted.
You then need to bind your checkbox with the initialized data.
Finally you need to watch the data to send an update to the database.
var vm = new Vue({
el: '#root',
data: {
//Your data
checked: null
},
// Function where you are going to fetch your data
mounted: function () {
console.log("Ajax call to initialize");
this.checked = Math.random() >= 0.5;
},
watch: {
// Watcher to save your data in the database
checked: function(newValue, oldValue){
if (oldValue === null) { return; } // to not make an useless update when data has been fetched
console.log("Ajax call to update value " + newValue);
}
}
});
<div id="root">
<input id="check" type="checkbox" name="active" v-model="checked" :disabled="checked === null">
<label for="check">Click me</label>
</div>
To fetch your data you can use for example Axios that works great with Vue.
To know more about life cycle of a component (to know if you should do the fetching at created or mounted) : https://v2.vuejs.org/v2/guide/instance.html

vuejs2 passing data between parent-child is wiping childs value

In VueJS 2 I am trying to create a component that gets and passes data back to the parent which then passes it to another component to display.
The component that gets the data has a user input field it uses to search. When I have it pass data back to the parent using $emit the value in the input keeps being wiped.
I am receiving the below mutation error but I haven't directly tried to change the userSearch field in the component so I am not sure why.
"Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "userSearch" (found in PersonField)"
Relevant html
<person-field v-on:event_child="eventChild"></person-field>
<person-search :prop="personListArray" ></person-search>
Parent app
var app = new Vue({
el: '#app',
data: {
personListArray : [],
tempArray: []
},
methods: {
eventChild: function (arr) {
this.personListArray = arr
}
}
})
Component 1, displays a user input. Uses the input to search and bring back data. Starts search when the length of the input is more then 2. As soon as you hit the 3rd character something is causing the input to clear which I don't want.
Vue.component('person-field', {
props: ['userSearch'],
template: '<input class="form-control" v-model="userSearch" >',
watch: {
userSearch: function () {
var arr = []
if (typeof this.userSearch !== 'undefined') { //added this because once i passed 3 characters in the field the userSearch variable becomes undefined
if (this.userSearch.length > 2) {
$.each(this.getUsers(this.userSearch), function (index, value) {
var obj = {
Title: value.Title,
ID: value.ID
}
arr.push(obj)
});
this.$emit('event_child', arr) //emits the array back to parent "eventChild" method
} else {
console.log('no length')
}
} else {
console.log('cant find field')
}
},
},
methods: {
getUsers: function (filter) {
//gets and returns an array using the filter as a search
return arr
},
}
});
Component 2 - based on the personListArray which is passed as a prop, displays the results as a list (this works)
Vue.component('person-search', {
props: ['prop'],
template: '<ul id="personList">' +
'<personli :ID="person.ID" v-for="person in persons">' +
'<a class="" href="#" v-on:click="fieldManagerTest(person.Title, person.ID)">{{person.Title}}</a>' +
'</personli></ul>',
computed: {
persons: function () {
return this.prop
}
},
methods: {
fieldManagerTest: function (title, ID) { //Remove item from users cart triggered via click of remove item button
//var user = ID + ';#' + title
//this.internalValue = true
//this.$emit('fieldManagerTest');
//this.$parent.$options.methods.selectManager(user)
},
},
});
Component 3, part of component 2
Vue.component('personli', {
props: ['ID'],
template: '<transition name="fade"><li class="moving-item" id="ID"><slot></slot></li></transition>'
})
;
The reason you get the warning,
Avoid mutating a prop directly since the value will be overwritten
whenever the parent component re-renders. Instead, use a data or
computed property based on the prop's value. Prop being mutated:
"userSearch" (found in PersonField)
Is because of this line
<input class="form-control" v-model="userSearch" >
v-model will attempt to change the value of the expression you've told it to, which in this case is userSearch, which is a property.
Instead, you might copy userSearch into a local variable.
Vue.component('person-field', {
props: ['userSearch'],
data(){
return {
searchValue: this.userSearch
}
},
template: '<input class="form-control" v-model="searchValue" >',
...
})
And modify your watch to use searchValue.
Here is an example.

Vue.js 2 - $forceUpdate() on components doesn't refresh computed properties?

I'm not sure if I'm doing this right or wrong, but all the answers I seem to find how to update the dom for computed values...
I have this component:
Vue.component('bpmn-groups', {
props: ['groups', 'searchQuery'],
template: '#bpmn-groups',
computed: {
filteredGroups: function () {
var self = this;
return this.groups.filter(function(group) {
self.searchQuery = self.searchQuery || '';
return _.includes( group.name.toLowerCase(), self.searchQuery.toLowerCase() );
});
}
},
methods: {
clearFilter: function () {
this.searchQuery = '';
},
deleteGroup: function(group) {
Vue.http.delete('api/groups/'+group.id ).then(response => { // success callback
var index = this.groups.indexOf(group); // remove the deleted group
this.groups.splice(index, 1);
this.$forceUpdate(); // force update of the filtered list?
toastr.success('Schemų grupė <em>'+group.name+'</em> sėkmingai pašalinta.');
}, response => { // error callback
processErrors(response);
});
this.$forceUpdate();
},
},
});
And in the template I just have a simple v-for to go through filteredGroups:
<input v-model="searchQuery" type="text" placeholder="Search..." value="">
<div v-for="group in filteredGroups" class="item">...</div>
The deletion works fine, it removes it from groups property, however the filteredGroups value still has the full group, until I actually perform a search or somehow trigger something else...
How can I fix it so that the filteredGroup is updated once the group is updated?
Don't mutate a prop - they are not like data defined attributes. See this for more information:
https://v2.vuejs.org/v2/guide/components.html#One-Way-Data-Flow
Instead, as recommended in the link, declare a local data attribute that is initialized from the prop and mutate that.