How to wait for parent prop - vue.js

I have a parent component, on which I set a data property:
created() {
carModels()
.then(x => {
this.carModels= [{ value: null, text: 'None' }].concat(x);
});
},
carModels is a prop that is passed to child components.
Currently, the prop is passed to the child before it is set in the above code. I have looked for a solution, but I am missing something...how do I wait for it, or, update the child once it has loaded? Presumably a promise would do this?

As per the direction in the comment from #Niklesh Raut, I resolved the issue using a watch in the child:
watch: {
carModels: function (carModels) {
console.log(carModels);
}
},

Related

Vue render function for draggable. How can I add v-model

I cannot find how to add v-model to draggable component in Vue2.
found some example in documentation, but it doesn't work.
I tried different variants like adding of list property to component, but it does not work.
const draggableItem = h('draggable', {
class: '',
attrs: {
'group':'people',
ghostClass: 'ghost',
animation: 200,
handle: '.drag-widget'
},
on: {
input: (event) => {
console.log('on input');
this.data.list = event.target.value
this.$emit('data.list', event.target.value)
},
end: () => {
console.log('drag on end');
this.handleMoveEnd()
},
add: (event) => {
console.log('drag on add', this.data.list);
console.log(event.dataTransfer);
this.handleWidgetAdd(event)
}
}
},
[transitionGroupItem]
);
Could somebody help me with it?
Tried to add to attributes list property like list: this.data.list, but it also doesn't work
V-model is basically combination of reactive prop :value and input. Value prop for initializing it and input event to update parent's version of it.
Here is very good guide - Adding V-model to custom vue component
On being added to the list, it should emit('input',data.list) and also there assign to a data value to avoid mutating the prop.

Vue $emit not works properly on dynamic component /promise

I have created a table component which accept dynamic data (th, tr, td,...).
Table data (td) could be a dynamic component as below:
<td>
<component
:is="data.content"
:colspan="data.colspan"
v-bind="data.props"
v-on="data.events"/>
</td>
...
export default {
name: "DynamicTable",
props: {
...
isLoading : { // loading slot will be used if true
type: Boolean,
default: false
}
}
}
I feed required data in another component like this:
<other-html-elements/>
<dynamic-table
:table-heads="tableHeads"
:table-rows="tableRows"
:is-loading="isLoading">
...
computed: { ...
tableRows () {...
new TableData(CancelOrderButton, 'component', {
props: {
order
},
events: {
'updateLoadingStatus': this.updateLoadingStatus
}
})
...
methods: { ...
updateLoadingStatus (status) {
this.isLoading = status
}
and here is my CancelOrderButton:
methods: {
cancelOrder () {
this.$emit('updateLoadingStatus', true)
somePromise().finally(() => {
this.$emit('updateLoadingStatus', false)
})
once I click on a button and invoke the cancelOrder method, the updateLoadingStatus will be emitted without any problem. and after the promise settled, it will be emitted again. but the handler will not triggered.
I have checked everything. I'm sure that events are emitted. this problem will be fixed when I move the second emit statement out of the finally block or I if do not pass isLoading as a props for the dynamicTable.
Try setting the prop for that emit like this:
<dynamic-table
:table-heads="tableHeads"
:table-rows="tableRows"
#update-loading-status="updateLoadingStatus"
:is-loading="isLoading">
And calling that emit like this (although it should work as you have it):
this.$emit('update-loading-status', true)
Also you can define them in a general way and use them in the component you want:
https://v2.vuejs.org/v2/guide/custom-directive.html

Vue deep cloning props in data is not responsive

So I have the following piss of code in my child component
props: {
prop_accounts: Array,
prop_pushing_destination: String,
prop_selected_account: String,
prop_selected: Boolean,
shop_settings: Object,
name_of_selected_account_field: String
},
data() {
return {
accounts: this._.cloneDeep(this.prop_accounts),
pushing_destination: this._.cloneDeep(this.prop_pushing_destination),
selected_account: this._.cloneDeep(this.prop_selected_account),
selected: this._.cloneDeep(this.prop_selected)
}
},
The parent props pass all the props and all seams to work well but the parent is constantly sampling the backend for changes and if they acquire it updates the props of child and although I can see that props are changed the data stays the same now if I throw the data and use props directly all works well but I get the following 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: "selected_account"
There are two ways you could handle this: use the props directly and emit changes to the parent; or use computed properties to compute the value based on the data and props.
Emitting changes is probably what you want to do 99% of the time as any changes to the props either internal or external will change what your component is doing. Using computed props allows for changes to the props to be used only if the data hasn't been modified internally.
Computed props
props: {
defaultAccounts: Array,
defaultSelected: Boolean,
...
}
data: () => ({
customAccounts: null,
customSelected: null,
})
computed: {
accounts() {
return (this.customAccounts == null) ? this.defaultAccounts : this.customAccounts
},
selected() {
return (this.customSelected == null) ? this.defaultSelected : this.customSelected
}
}
You could even define setters on the computed props to set the value of the data properties.
Emit changes
Component:
props: {
accounts: Array,
selected: Boolean,
...
}
methods: {
accountsChanged(value) {
this.$emit('update:accounts', value)
},
selectedChanged(value) {
this.$emit('update:selected', value)
}
}
Where you use component:
<my-component :accounts.sync="accounts" :selected.sync="false"></my-component>
See Sync Modifier Documentation for more info.
I haven't tested this code so it may need tweaking to get it working correctly.

vue: how to make the object passed to component reactive?

Codepen Demo
I have a component which has an location object as props. The argument I passed in is locations[index] which is a selected item from a locations array.
However, the component cannot react when the index change. As you can see in the demo, the JSON change as you click the button, but the component cannot update.
What's the best way to make the component reactive?
Your location component populates the province and city data properties in the mounted hook only. When the location prop changes, the mounted hook will not be called again, so you are left with stale data.
Use a computed property instead:
computed: {
province() {
return this.location.province;
},
city() {
return this.location.city;
}
}
Updated codepen
If you really do require province and city to be data properties (and not computed properties) then you will need to watch the location prop to update the properties:
data() {
return {
province: null,
city: null
}
},
watch: {
location: {
immediate: true,
handler(loc) {
this.province = loc.province;
this.city = loc.city;
}
}
}
Nested items in your prop location won't be reactive, you'll need to use something like lodash deepClone :
<location :location.sync="_.deepClone(loc)"></location>
That's it, no need for watchers or anything else.

How to change the value of a prop (or data) of a component, from OUTSIDE the component?

As the title says, I'm trying to change the value of a prop/data in a component, but the trigger is being fired from outside the component, from something that has nothing to do with Vuejs.
Currently I trying to use a Simple State Manager, based on the docs from here, like so:
var store = {
debug: true,
state: {
progress: 23
},
setProgress (uff) {
if (this.debug) console.log(uff)
this.state.progress = uff
}
}
The documentation leads me to believe that if the value of progress is mutated, the value of my Vue instance would also change if I link them accordingly. But this doesn't work in a component (my guess would be it's cause it's a function).
This is part of my component:
Vue.component('transcoding', {
data () {
return {
progress: store.state.progress
}
},
template: `
<v-progress-circular
:size="130"
:width="25"
:value="progress"
color="teal"
>
{{progress}}
</v-progress-circular>
`
})
So, when I trigger a store.setProgress(value), nothing happens. The log messages do happen, but the state isn't updated in the component.
This is the script that's currently triggering the state change:
App.cable.subscriptions.create(
{ channel: "ProgressChannel", room: "2" },
{ received: function() {
store.setProgress(arguments[0])
}}
)
It's an ActionCable websocket in Ruby on Rails. The trigger works perfectly, but I just cannot make the connection between the state change and the component.
I tried loading this script in the mounted() event for the component, thinking I could reference the value like this:
Vue.component('transcoding', {
data () {
return {
progress: 0
}
},
template: `
<v-progress-circular
:size="130"
:width="25"
:value="progress"
color="teal"
>
{{progress}}
</v-progress-circular>
`,
methods: {
setProgress: function(uff) {
this.progress = uff
}
},
mounted() {
App.cable.subscriptions.create(
{ channel: "ProgressChannel", room: "2" },
{ received: function() {
this.setProgress(arguments[0])
}}
)
}
})
But this gives me an error saying that this.setProgress is not a function, which is obvious since I'm calling it within the create method of App.cable.subscriptions.
How can I make this work? I realize I'm mixing things with my question, but I wanted to illustrate what my goal is. I simply want to know how to make the component's progress data to update, either from the outside, or from the component itself if I can make it find the function.
You are initializing your data item to the value from the store:
data () {
return {
progress: store.state.progress
}
}
Changes to the store will not propagate to your data item. You could eliminate the data item and just use store.state.progress where you need it, or you could create an computed that returns its value if you want a local single-name handle for it.