Why does Vuetify Autocomplete not selecting the data being set? - vue.js

I use Vuetify autocomplete as a reusable component to display a list of jobs in key/value pairs. In creating record, the component works fine but when editing where data should be filled in, the data has the value but not showing on the component.
JobDropdownSelector.vue:
................................................................................................................................................
<template>
<v-autocomplete
v-model="job"
label="Job Designation"
item-value="id"
item-text="name"
return-object
:items="jobs"
#change="onChange"
>
</v-autocomplete>
</template>
<script>
export default {
props: {
selectedJob: {
type: Object,
default: null
}
},
data() {
return {
jobs: [],
job: null
};
},
methods: {
getDataFromApi() {
axios.get("api/jobs")
.then(response => {
this.jobs = response.data.data;
})
.catch(error => {
console.log(error);
reject();
});
},
onChange(e) {
this.job = e;
this.$emit("onChange", e);
}
},
watch: {
selectedJob: {
deep: true,
immediate: true,
handler(newValue, oldValue) {
this.job = newValue;
}
}
},
mounted() {
this.getDataFromApi();
}
};
</script>
EditForm.vue:
................................................................................................................................................
<template>
<div>
<JobDropdownSelector
v-model="job"
:selectedJob="job"
#onChange="onChangeJob"
>
</JobDropdownSelector>
</div>
</template>
<script>
import JobDropdownSelector from "../components/JobDropdownSelector";
export default {
components: {
JobDropdownSelector
},
data() {
return {
job: null
};
},
methods: {
onChangeJob(e) {
this.job = e;
},
getInitialJob() {
axios.get("api/jobs/22").then(response => {
this.job = response.data.data;
});
}
},
mounted() {
this.getInitialJob();
}
};
</script>
Display
Console

It looks like you overriding job twice: on the onChangeJob method and via v-model on JobDropdownSelector.
You can try use input event to update modek instead of emitting additional event.
JobDropdownSelector.vue
methods: {
onChange(e) {
// You don't need set job because it's connected via v-model.
// this.job = e;
this.$emit("input", e);
}
}
This will cause that job in EditForm.vue should update automatically after dropdown change.

Related

How to forceUpdate sibling component in VueJS

I have created a component which have two child components AddContactList and ViewContactLists, Here I need to forceUpdate ViewContactList when new entry inserted from AddContactList component
This is AddContactList components script
<script>
export default {
data() {
return {
fields: {},
errors: {},
success: false,
loaded: true,
}
},
methods: {
submit() {
if (this.loaded) {
this.loaded = false;
this.success = false;
this.errors = {};
console.log('Loading..');
axios.post('/submit', this.fields).then(response => {
this.fields = {}; //Clear input fields.
this.loaded = true;
this.success = true;
console.log('done..');
// --Here I need to update ViewContactList component
}).catch(error => {
this.loaded = true;
if (error.response.status === 422) {
console.log(error.response.data.errors)
this.errors = error.response.data.errors || {};
}
});
}
},
},
}
</script>
This is ViewContactList components script
<script>
import pagination from 'laravel-vue-pagination'
export default {
name:"ContactList",
components:{
pagination
},
data(){
return {
ContactList:{
type:Object,
default:null
}
}
},
mounted(){
this.list()
},
methods:{
async list(page=1){
await axios.get(`/getContactLists?page=${page}`).then(({data})=>{
this.ContactList = data
}).catch(({ response })=>{
console.error(response)
})
}
}
}
</script>
You can simply achieve this by emitting an event on successful save to the parent and then from parent component you can invoke the contact list component method with the help of ref.
Live demo :
Vue.component('childone', {
props: ['childmsg', 'childOneRef'],
template: `<p>{{ childmsg }} <button #click="$emit('savesuccess')">Add Contact</button></p>`
});
Vue.component('childtwo', {
props: ['childmsg', 'childoneref'],
template: '<p>{{ childmsg }}</p>',
methods: {
getupdtedList() {
console.log('Contact List call');
}
}
});
var app = new Vue({
el: '#app',
methods: {
callViewContactListCompMethod() {
this.$refs.contactListRef.getupdtedList();
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<childone #savesuccess="callViewContactListCompMethod" childmsg="This is a child 1 message">
</childone>
<childtwo ref="contactListRef" childmsg="This is a child 2 message">
</childtwo>
</div>
If your parent component is ContactComponent
ContactComponent
|--AddContactList
|--ViewContactList
When you insert contact in add contact list emit the #addList event to the parent component.
Then pass the contact list as a props to ViewContactList.
when the props is changed, the ViewContactList component will be re-rendered automatically.

How to react data changes in vue component?

I am trying to build a component that should update the string using setInterval.
Here is the code of the component.
<script>
export default {
data() {
return {
offers: [
'1!',
'2!',
'3!',
],
current_index: 0,
indexInterval: null,
}
},
created() {
this.updateToastIndex()
},
beforeDestroy() {
clearInterval(this.indexInterval)
},
methods: {
updateToastIndex() {
const indexInterval = setInterval(() => {
this.$nextTick(() => {
if (this.current_index === 2) {
this.current_index = 0
console.log(this.offers[this.current_index], 'changedprev')
} else {
this.current_index = this.current_index + 1
console.log(this.offers[this.current_index], 'changed')
}
})
}, 1000)
this.indexInterval = indexInterval
},
},
}
</script>
<template>
<div>{{ offers[current_index] }}</div>
</template>
I can see the current_index in updateToastIndex function getting updated but current_index in template is not getting updated.
Please suggest some solution

Variable not updated after vuex mutation

I am creating a settings page, where I fetch some data from the API and I am using Vuex to handle mutations.
I can see that the Vuex completes properly, but value for my dailyCount variable doesn't update in frontend.
This is my Settings component:
<template>
<div>
<div class="row col">
<h1>Settings</h1>
</div>
<div class="row col">
<div class="well">
<form class="form-inline">
<input type="number" v-model="dailyCount" />
{{ dailyCount }}
</form>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'settings',
data () {
return {
dailyCount: 500
};
},
created () {
this.$store.dispatch('settings/fetchSetting');
},
computed: {
isLoading() {
return this.$store.getters['user/isLoading'];
},
hasError() {
return this.$store.getters['user/hasError'];
},
error() {
return this.$store.getters['user/error'];
},
},
}
</script>
I do mutations here:
import SettingsAPI from '../api/settings';
export default {
namespaced: true,
state: {
isLoading: false,
error: null,
settings: null,
},
getters: {
isLoading (state) {
return state.isLoading;
},
hasError (state) {
return state.error !== null;
},
error (state) {
return state.error;
},
user (state) {
return state.user;
},
},
mutations: {
['FETCHING_SETTINGS'](state) {
state.isLoading = true;
state.error = null;
state.settings = null;
},
['FETCHING_SETTINGS_SUCCESS'](state, settings) {
state.isLoading = false;
state.error = null;
state.settings = settings;
},
['FETCHING_SETTINGS_ERROR'](state, error) {
state.isLoading = false;
state.error = error;
state.settings = null;
},
},
actions: {
fetchSetting ({commit}) {
commit('FETCHING_SETTINGS');
return SettingsAPI.get()
.then(res => {commit('FETCHING_SETTINGS_SUCCESS', res.data);})
.catch(err => commit('FETCHING_SETTINGS_ERROR', err));
},
},
}
And call to a server is done here (api/settings.js - it is imported in mutation file):
import axios from 'axios';
export default {
get() {
return axios.get('/user');
},
}
Can you see what am I doing wrong? I am trying to debug it using Vuejs debug toolbar, but all seems to work fine.
You need to get store state from vuex and inject to Vue component, either by this.$store.state or this.$store.getters.
For example:
<script>
export default {
name: 'settings',
data () {
return {
dailyCount: 500
};
},
created () {
this.$store.dispatch('settings/fetchSetting');
},
computed: {
isLoading() {
return this.$store.getters['user/isLoading'];
},
hasError() {
return this.$store.getters['user/hasError'];
},
error() {
return this.$store.getters['user/error'];
},
settings() {
return this.$store.state.settings
}
},
watch: {
settings () {
this.dailyCount = this.settings.dailyCount
}
}
}
</script>

Updating a prop inside a child component so it updates on the parent container too

So I have a simple template like so:
<resume-index>
<div v-for="resume in resumes">
<resume-update inline-template :resume.sync="resume" v-cloak>
//...my forms etc
<resume-update>
</div>
<resume-index>
Now, inside the resume-updatecomponent I am trying to update the prop on the inside so on the outside it doesn't get overwritten, my code is like so;
import Multiselect from "vue-multiselect";
import __ from 'lodash';
export default {
name: 'resume-update',
props: ['resume'],
components: {
Multiselect
},
data: () => ({
form: {
name: '',
level: '',
salary: '',
experience: '',
education: [],
employment: []
},
submitted: {
form: false,
destroy: false,
restore: false
},
errors: []
}),
methods: {
update(e) {
this.submitted.form = true;
axios.put(e.target.action, this.form).then(response => {
this.resume = response.data.data
this.submitted.form = false;
}).catch(error => {
if (error.response) {
this.errors = error.response.data.errors;
}
this.submitted.form = false;
});
},
destroy() {
this.submitted.destroy = true;
axios.delete(this.resume.routes.destroy).then(response => {
this.resume = response.data.data;
this.submitted.destroy = false;
}).catch(error => {
this.submitted.destroy = false;
})
},
restore() {
this.submitted.restore = true;
axios.post(this.resume.routes.restore).then(response => {
this.resume = response.data.data;
this.submitted.restore = false;
}).catch(error => {
this.submitted.restore = false;
})
},
reset() {
for (const prop of Object.getOwnPropertyNames(this.form)) {
delete this.form[prop];
}
}
},
watch: {
resume: function() {
this.form = this.resume;
},
},
created() {
this.form = __.cloneDeep(this.resume);
}
}
When I submit the form and update the this.resume I get the following:
[Vue warn]: 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: "resume"
I have tried adding computed to my file, but that didn't seem to work:
computed: {
resume: function() {
return this.resume
}
}
So, how can I go about updating the prop?
One solution:
simulate v-model
As Vue Guide said:
v-model is essentially syntax sugar for updating data on user input
events, plus special care for some edge cases.
The syntax sugar will be like:
the directive=v-model will bind value, then listen input event to make change like v-bind:value="val" v-on:input="val = $event.target.value"
So the steps:
create one prop = value which you'd like to sync to parent component
inside the child component, create one data porperty=internalValue, then uses Watcher to sync latest prop=value to data property=intervalValue
if intervalValue change, emit one input event to notice parent component
Below is one simple demo:
Vue.config.productionTip = false
Vue.component('container', {
template: `<div>
<p><button #click="changeData()">{{value}}</button></p>
</div>`,
data() {
return {
internalValue: ''
}
},
props: ['value'],
mounted: function () {
this.internalValue = this.value
},
watch: {
value: function (newVal) {
this.internalValue = newVal
}
},
methods: {
changeData: function () {
this.internalValue += '#'
this.$emit('input', this.internalValue)
}
}
})
new Vue({
el: '#app',
data () {
return {
items: ['a', 'b', 'c']
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<div>
<p>{{items}}
<container v-for="(item, index) in items" :key="index" v-model="items[index]">
</container>
</div>
</div>
or use other prop name instead of value (below demo use prop name=item):
Also you can use other event name instead of event name=input.
other steps are similar, but you have to $on the event then implement you own handler like below demo.
Vue.config.productionTip = false
Vue.component('container', {
template: `<div>
<p><button #click="changeData()">{{item}}</button></p>
</div>`,
data() {
return {
internalValue: ''
}
},
props: ['item'],
mounted: function () {
this.internalValue = this.item
},
watch: {
item: function (newVal) {
this.internalValue = newVal
}
},
methods: {
changeData: function () {
this.internalValue += '#'
this.$emit('input', this.internalValue)
this.$emit('test-input', this.internalValue)
}
}
})
new Vue({
el: '#app',
data () {
return {
items: ['a', 'b', 'c']
}
},
methods: {
syncChanged: function (target, index, newData) {
this.$set(target, index, newData)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<div>
Event Name=input
<p>{{items}}</p>
<container v-for="(item, index) in items" :key="index" :item="item" #input="syncChanged(items, index,$event)">
</container>
</div>
<hr> Event Name=test-input
<container v-for="(item, index) in items" :key="index" :item="item" #test-input="syncChanged(items, index,$event)">
</container>
</div>
I usually use vuex to manage variables that I will be using in multiple components and like the error says, load them in the various components using the computed properties. Then use the mutations property of the store object to handle changes
In component files
computed: {
newProfile: {
get() {
return this.$store.state.newProfile;
},
set(value) {
this.$store.commit('updateNewProfile', value);
}
},
In the vuex store
state: {
newProfile: {
Name: '',
Website: '',
LoginId: -1,
AccountId: ''
}
},
mutations: {
updateNewProfile(state, profile) {
state.newProfile = profile;
}
}

Computed property "main_image" was assigned to but it has no setter

How can I fix this error "Computed property "main_image" was assigned to but it has no setter"?
I'm trying to switch main_image every 5s (random). This is my code, check created method and setInterval.
<template>
<div class="main-image">
<img v-bind:src="main_image">
</div>
<div class="image-list>
<div v-for="img in images" class="item"><img src="img.image"></div>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'Item',
data () {
return {
item: [],
images: [],
}
},
methods: {
fetchImages() {
axios.get(`/api/item/${this.$route.params.id}/${this.$route.params.attribute}/images/`)
.then(response => {
this.images = response.data
})
.catch(e => {
this.images = []
this.errors.push(e)
})
},
},
computed: {
main_image() {
if (typeof this.item[this.$route.params.attribute] !== 'undefined') {
return this.item[this.$route.params.attribute].image_url
}
},
},
watch: {
'$route' (to, from) {
this.fetchImages()
}
},
created () {
axios.get(`/api/item/${this.$route.params.id}/`)
.then(response => {
this.item = response.data
})
.catch(e => {
this.errors.push(e)
})
this.fetchImages();
self = this
setInterval(function(){
self.main_image = self.images[Math.floor(Math.random()*self.images.length)].image;
}, 5000);
},
}
</script>
Looks like you want the following to happen...
main_image is initially null / undefined
After the request to /api/item/${this.$route.params.id}/ completes, it should be this.item[this.$route.params.attribute].image_url (if it exists)
After the request to /api/item/${this.$route.params.id}/${this.$route.params.attribute}/images/ completes, it should randomly pick one of the response images every 5 seconds.
I'd forget about using a computed property as that is clearly not what you want. Instead, try this
data() {
return {
item: [],
images: [],
main_image: '',
intervalId: null
}
},
methods: {
fetchImages() {
return axios.get(...)...
}
},
created () {
axios.get(`/api/item/${this.$route.params.id}/`).then(res => {
this.item = res.data
this.main_image = this.item[this.$route.params.attribute] && this.item[this.$route.params.attribute].image_url
this.fetchImages().then(() => {
this.intervalId = setInterval(() => {
this.main_image = this.images[Math.floor(Math.random()*this.images.length)].image;
})
})
}).catch(...)
},
beforeDestroy () {
clearInterval(this.intervalId) // very important
}
You have to add setter and getter for your computed proterty.
computed: {
main_image: {
get() {
return typeof this.item[this.$route.params.attribute] !== 'undefined' && this.item[this.$route.params.attribute].image_url
},
set(newValue) {
return newValue;
},
},
},