How pass object, contains computed property, to child? - vue.js

<template>
child-component(
v-for="item in items"
:item="item"
)
</template>
<script>
data() {
return {
items: [],
valueFromApi: null,
}
},
computed: {
someCompProp() { return val // math based on valueFromApi }
},
created: {
setInterval(() => { // make api call and set valueFromApi }, 2000)
this.createItems();
},
methods: {
createItems() {
// ...someActions
formedItems.forEach((item) => {
this.items.push({
...item,
someValue: this.someCompProp,
})
})
this.items.push(item)
},
apiCall() {
// store result to valueFromApi
}
</script>
Now it's not reactive.
I can achive reactivity only passing computed prop like independed property.
Can computed property someCompProp be reactive in child prop item obj?

Okay, its done, i just return computed prop in someValue. someValue: () => this.someCompProp,

Related

Vue computed methods can't change data using setter in template

I need change data using computed:
<template>
<div>{{ userDataTest }}</div>
</template>
props: {
exampleData: {
type: Object,
required: true,
},
},
computed: {
userDataTest: {
get: function() {
return this.exampleData;
},
set: function(newValue) {
console.log(newValue);
return newValue;
},
},
}
mounted () {
setTimeout(() => {
console.log('Change now to null!');
this.userDataTest = null;
}, 5000);
},
I get data using props, next I create computed methods with getter and setter. I added userDataTest in <template>. And the I change (using mounted) data in this.userDataTest to null using setter.
In console.log(newValue); in setter I see newValue is null, but in <template> nothing change still I have data from getter.
Why setter not change data in <template> to null ?
It seems you're trying to set the computed property's value by returning a new value, but Vue doesn't actually check the setter's return value. Perhaps you were trying to proxy a data variable through a computed property. If so, the setter should set that data variable in the setter body.
For instance, your component could declare a data variable, named userData, which always has the latest value of the exampleData prop through a watcher:
export default {
props: {
exampleData: Object
},
data() {
return {
userData: {}
}
},
watch: {
exampleData(exampleData) {
this.userData = exampleData
}
},
}
Then, your template and computed prop would use userData instead:
<template>
<div>{{ userData }}</div>
</template>
<script>
export default {
//...
computed: {
userDataTest: {
get() {
return this.userData
},
set(newValue) {
this.userData = newValue
}
}
}
}
</script>
Mutating a prop locally is considered an anti-pattern
However, you can use the .sync modifier as shown below, but you can't set the prop to null because you are specifying that it has to be an Object type.
Vue.component('my-component', {
template: `<div>{{ userDataTest }}</div>`,
props: {
exampleData: {
type: Object,
required: true
}
},
computed: {
userDataTest: {
get: function() {
return this.exampleData
},
set: function(newValue) {
this.$emit('update:exampleData', newValue)
}
}
},
mounted() {
setTimeout(() => {
console.log('Change now!')
this.userDataTest = {}
}, 2500)
}
})
new Vue({
el: '#app',
data() {
return {
exampleData: {
foo: 'bar'
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<div id="app">
<my-component :example-data.sync="exampleData"></my-component>
</div>

How load data after all computed data from vuex store was loaded?

Working with Laravel 5.7 / Vuejs 2.6 / Vuex 3.1 app I use "vue-select": "^2.6.4" and when I open form with existing data I need
to fill my select component with variable
selection_status = {key: ‘key’, label: ‘label’}
<v-select
v-model="selection_status"
data-vv-name="userRow.status"
:options="customerStatusValueArray"
v-validate="'required'"
id="status"
name="status"
class="form-control editable_field"
placeholder="Select option"
></v-select>
data() {
return {
...
selection_status: null,
mounted() {
this.loadCustomer();
}, // mounted() {
loadCustomer() {
axios.get(window.API_VERSION_LINK + 'personal/customers/' + this.user_id).then((response) => {
this.userRow = response.data.user;
this.customerStatusValueArray.map((nextCustomerStatusValue, index) => {
if (nextCustomerStatusValue.key == this.userRow.status) {
this.selection_status = {key: this.userRow.status, label: nextCustomerStatusValue.label};
}
});
...
computed: {
customerStatusValueArray() {
return this.$store.getters.customerStatusValueArray;
},
I get customerStatusValueArray array from vuex store and I found that it is filled AFTER method loadCustomer from mounted is loaded,
so this.selection_status is null.
Is there is a way to run
this.loadCustomer();
after all computed data from vuex store is loaded?
Thanks!
You probably should use a loading state,
also consider using created() to call this.loadCustomer()
because mounted() its called after the component has been rendered.
<v-select v-if="loaded' />
<my-loader v-else/>
data() {
loaded: false,
}
loadCustomer() {
axios.get('url').then((response) => {
this.loaded = true;
});
}
update:
You can observe when your computed changes,
once customerStatusValueArray isn't empty you can call this.loadCustomer()
export default {
watch: {
customerStatusValueArray: (newVal, oldVal) {
if (newVal.length > 0)
this.loadCustomer()
}
}
computed: {
customerStatusValueArray() {
return this.$store.getters.customerStatusValueArray;
},
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

Vue.js - data value does not set from prop

What do I have: two components, parent and child.
Parent
<UserName :name=user.name></UserName>
...
components: {UserName},
data() {
return {
user: {
name: '',
...
}
}
},
created() {
this.fetchUser()
console.log(this.user) //<- object as it is expected
},
methods: {
fetchUser() {
let that = this
axios.get(//...)
.then(response => {
for (let key in response.data) {
that.user[key] = response.data[key]
}
})
console.log(that.user) //<- object as it is expected
}
}
Child
<h3 v-if="!editing" #click="edit">{{ usersName }}</h3>
<div v-if="editing">
<div>
<input type="text" v-model="usersName">
</div>
</div>
...
props: {
name: {
type: String,
default: ''
},
},
data() {
return {
editing: false,
usersName: this.name,
...
}
},
Problem: even when name prop is set at child, usersName data value is empty. I've inspected Vue debug extension - same problem.
What have I tried so far (nothing helped):
1) props: ['name']
2)
props: {
name: {
type: String
},
},
3) usersName: JSON.parse(JSON.stringify(this.name))
4) <UserName :name="this.user.name"></UserName>
P. S. when I pass static value from parent to child
<UserName :name="'just a string'"></UserName>
usersName is set correctly.
I've also tried to change name prop to some foobar. I guessed name might conflict with component name exactly. But it also didn't helped.
user.name is initially empty, and later gets a value from an axios call. usersName is initialized from the prop when it is created. The value it gets is the initial, empty value. When user.name changes, that doesn't affect the already-initialized data item in the child.
You might want to use the .sync modifier along with a settable computed, or you might want to put in a watch to propagate changes from the prop into the child. Which behavior you want is not clear.
Here's an example using .sync
new Vue({
el: '#app',
data: {
user: {
name: ''
}
},
methods: {
fetchUser() {
setTimeout(() => {
this.user.name = 'Slartibartfast'
}, 800);
}
},
created() {
this.fetchUser();
},
components: {
userName: {
template: '#user-name-template',
props: {
name: {
type: String,
default: ''
}
},
computed: {
usersName: {
get() { return this.name; },
set(value) { this.$emit('update:name', value); }
}
},
data() {
return {
editing: false
}
}
}
}
});
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
{{user.name}}
<user-name :name.sync=user.name></user-name>
</div>
<template id="user-name-template">
<div>
<input type="text" v-model="usersName">
</div>
</template>
should be passed like this...
<UserName :name="user.name"></UserName>
if data property is still not being set, in mounted hook you could set the name property.
mounted() {
this.usersName = this.name
}
if this doesn't work then your prop is not being passed correctly.
sidenote: I typically console.log within the mounted hook to test such things.

Make computed Vue properties dependent on current time?

I have a collection of Events. These will render in lists according to their status - upcoming/live/previous. Thus the rendering is dependent on the current time. How can I make the computed properties to update/recompute as time goes by?
<template>
<div>
<h2>Upcoming events</h2>
<p v-bind:key="event.name" v-for="event in upcomingEvents">{{ event.name }}</p>
<h2>Live events</h2>
<p v-bind:key="event.name" v-for="event in liveEvents">{{ event.name }}</p>
<h2>Previous events</h2>
<p v-bind:key="event.name" v-for="event in previousEvents">{{ event.name }}</p>
</div>
</template>
<script>
import Event from '../Event.js'
export default {
data() {
return {
events: []
}
},
computed: {
upcomingEvents() {
return this.events.filter(event => event.isUpcoming())
},
liveEvents() {
return this.events.filter(event => event.isLive())
},
previousEvents() {
return this.events.filter(event => event.isPrevious())
},
},
mounted() {
// this.events are populated here
}
}
</script>
You can declare a time-dependent data variable and use setInterval() to update it:
data() {
return {
events: [],
now: Date.now()
}
},
created() {
var self = this
setInterval(function () {
self.now = Date.now()
}, 1000)
},
computed: {
upcomingEvents() {
return this.events.filter(event => event.isUpcoming(this.now))
},
liveEvents() {
return this.events.filter(event => event.isLive(this.now))
},
previousEvents() {
return this.events.filter(event => event.isPrevious(this.now))
}
}
Note that you need to use now in computed properties to make them update.
One possibility for your case is $forceUpdate(). However, it should be note that it will work specifically for your case because you're NOT using child components.
If you were to use child components, you would then need to use slots within the parent component and insert the children within their respective slots.
So, for example, you could do:
created() {
setInterval(() => {
this.$forceUpdate()
}, 5000)
}
Which will cause the entire component to re-render. This may or may not be the desirable interaction you're looking for.
You could create a time variable that you update every second then use this variable in your computed properties.
new Vue({
el: "#app",
data: {
time: ''
},
computed: {
computedValue: function(){
return this.time;
}
},
mounted: function(){
var app = this;
setInterval(function(){
app.time = parseInt(new Date().getTime() / 1000);
}, 1000);
}
})
https://jsfiddle.net/ecwnvudz/

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;
}
}