Mounted in child component executes before parent component created issue - javascript

I am working on vue application. The issue I am facing here is that the child component mounted is executed before the parent child created.
I am sending props from parent component to child component and I want to use those props in the child component's mounted but I am not getting those props in mounted as they are executed before created in parent. Please help me fix this issue so that child component sets this.userCopy to this.user which is coming as a prop.
parent componeent
<template>
<div>
<Info :user="user" />
</div>
</template>
<script>
import Info from 'views/info';
export default {
components: {
Info
},
data () {
return {
user: {
first_name: '',
last_name: '',
},
errors:[]
}
}
created() {
this.fetchUsers();
},
methods: {
fetchUsers() {
this.$axios.get('/user.json')
.then(response => {
this.user = response.data;
}).catch(error => {
this.errors = error.response.data.error;
});
},
}
}
</script>
child component
<template>
<div>
{{userCopy}}
</div>
</template>
<script>
export default {
props: ['user'],
data () {
return {
userCopy: {}
}
}
mounted: function() {
var that = this;
this.userCopy = this.user
}
}
</script>

Since user is updated asynchronously after the component is already mounted, user would be the initial value (undefined) in the mounted() hook.
Option 1: The parent could conditionally render the child component based on user, so that the component's user prop would have a value upon mounting:
<Info v-if="user" :user="user">
export default {
data() {
return {
user: null, // updated asynchronously in fetchUsers()
}
}
}
Option 2: The child could use a watcher on user that updates userCopy:
export default {
//...
watch: {
user(user) {
this.userCopy = { ...user } // shallow copy
}
}
}
Note the use of the spread operator to shallow copy user.

Actually, your created is being invoked before your children component's mounted. The problem is that the fetchUsers is asynchronous (a Promise) and needs to be awaited.
async created() {
await this.fetchUsers();
},
Try this code, awaiting the asynchronous operation.

Something I've done is have a loaded value in my data. Then on that child component that relies on that data, I'll throw a v-if="loaded".
data() {
return {
loaded: false
}
},
async created() {
try {
await this.fetchUsers();
// loaded will be set after the users have been fetched.
this.loaded = true;
}
catch(error) {
console.error('Failed to grab the users', error)
}
}
then in your template just do...
<child-component v-if="loaded" />

Related

Vuex setting state

I have a prop that I'm binding to a child component. I'm trying to get rid of this and set the value to a data in a vuex global state.
<VideoPip :asset="asset" v-show="pipEnabled === true" />
How can I set this.$store.state.assetVideo; which is by default an empty object equal to the asset value? Would I do this in a computed property?
For reading the video state data, you can just use the mapState helper. For example, in your Video component
import { mapState } from 'vuex'
export default {
name: 'Video',
computed: mapState(['assetVideo'])
}
You can then reference this.assetVideo in your component's methods and assetVideo in its template. This will be reactive to changes in the store.
For setting the value, you should (must) use a mutation. For example
const store = new Vuex.Store({
strict: true, // always a good idea
state: {
assetVideo: {} // personally, I'd default to "null" but that's up to you
},
mutations: {
setVideoAsset: (state, assetVideo) => (state.assetVideo = assetVideo)
}
}
and in your components
methods: {
selectVideo (video) {
this.$store.commit('setVideoAsset', video)
}
}
in your parent component:
// if you dont use namespace
import { mapMutations, mapState } from 'vuex'
export default {
computed: {
...mapState(['assetVideo']),
asset: {
get() {
return this.assetVideo
},
set(newValue) {
this.setAssetVideo(newValue)
}
}
},
methods: {
...mapMutations(['setAssetVideo']),
}
}
store
const store = {
state: {
assetVideo: {}
},
mutations: {
setAssetVideo(state, payload) {
state.assetVideo = payload
}
}
}
in your parent component you can change the state using this.asset = 'something' and it will change in store and anywhere else used
and also you can pass it to child components

Render a child component only after async method is completed in parent's mounted

I have a parent component which renders a child component. I am making an async call (an axios 'get' request from Vuex actions) from inside the mounted of the parent component, which fetches the data required for the child component.
I want to prevent the rendering of the child component til that async call is completed. Can someone suggest some elegant approach?
v-if
<child v-if="mydata" />
mydata can be a data property initialized to null:
data() {
return {
mydata: null
}
}
When that's populated in created/mounted, the child component will show.
async created() {
const response = await axios // ...
this.mydata = response.data;
}
EDIT: Based on your comments below. For Vuex, do this instead:
Continue using the v-if
Use a computed instead of a data property:
computed: {
mydata() {
return this.$store.state.mydata;
}
}
alternate syntax using mapState
import { mapState } from 'vuex';
computed: {
...mapState(['mydata'])
}

What is the best way to save async data in Vue?

I have a following store module:
const state = {
user: {}
}
const mutations = {
saveData(state, payload) {
state.user = payload
}
}
const actions = {
async loadData({ commit }, payload) {
try {
const res = await axios.get('https://api.example.com/user/1')
commit('saveData', res.data.data)
} catch(e) {
console.log(e)
}
}
}
const getters = {
getData(state) {
return state.user
}
}
Now what's the best way to save the data in component? Is it using watch
import { mapGetters } from 'vuex'
export default {
data() {
return {
user: {}
}
},
computed: {
...mapGetters({
getData
})
},
watch: {
'$store.state.users.users'() {
this.user = this.getData
}
}
}
... or store.subscribe?
import { mapGetters } from 'vuex'
export default {
data() {
return {
user: {}
}
},
computed: {
...mapGetters({
getData
})
},
created() {
this.$store.subscribe((mutation, state) => {
if(mutation.type === 'saveData') {
this.user = this.getData
}
})
}
}
Since you already know about store mapping I suppose you try to have some kind of edit form where you need the actual data taken from the database and also the ability to change this data to later send it back to the database.
You don't need a getter to have a simple reference to a store item. You will be very fine with mapState in your component:
{
computed: {
...mapState({
user: state => state.user,
}),
}
}
So as soon as user changed in the store your component will know about it. And here you can update the object you're editing. Let's rename it to edit to avoid collision:
{
data() {
return {
edit: {},
}
},
computed: {
...mapState({
user: state => state.user,
}),
},
watch: {
user: {
immediate: true,
handler(user) {
this.edit = { ...user }
},
},
},
}
Now edit is updated accordingly even if the component was mounted after the store item was updated (thanks to immediate option), and you can safely modify it in your component without any impact on the store reference.
P.S. Have to mention that in this implementation if you want to have reactivity on fields within edit object, then you need to update the whole edit object on each it's field update like this: this.edit = {...this.edit, [prop]: value}. But if you want it to be the natural Vue way, then first you need to initialize edit with actual object structure, and in the watcher for user perform something like Object.assign(this.edit, user).
It's preferable to use computed properties to access store data and keep it reactive.
It's possible to create a computed property using mapGetters as you do in the shared snipped, however, taking into account the getter is simply returning user from state, I don't think you need a getter at all, you can simply map the value from state by using mapState helper. In this way the component would be simplified to something like as follows:
import { mapState } from 'vuex'
export default {
computed: {
...mapState([
'user'
])
}
}
With the above approach you can reference user as this.user in the component's methods or simply as user in template of the component. Also, since the getter is out of use, you can delete the getter definition from the store (unless you are using it anywhere else).

Load state with async action Vuex

I can't realize how to get it works.
I'm trying to load a propertie (productos) on my data() which has to catch the value from a state.
My component:
data () {
return {
productos: this.$store.state.mStock.productos
}
},
created() {
this.$store.dispatch('fetchProductos')
}
At this point i think it's okay, when i load my component i dispatch my action to load the state on the store.
I think the problem is that the way i fill the state is ASYNC
Store:
import StockService from '#/services/StockService'
export const moduleStock = {
strict: false,
state: {
productos: []
},
mutations: {
setProductos (state, payload) {
state.productos = payload.productos
}
},
actions: {
async fetchProductos ({commit}, payload) {
const resp = await (StockService.getProductos())
var productos = resp.data
commit('setProductos', {productos: productos})
}
}
}
When i load my component, the propertie "productos" on the data() is null, however if i see the 'state.productos' from the Vuex devtools, it has the data!
I'm messed up.
The data() method is only run once.
This might seem to work if when the component and the vue store use the same object instance, but doesn't work in this case because a new array instance is assigned in the store while the component stil has the previous instance (the empty array)
Use computed properties. I recommend using the mapState() helper:
computed: mapState({
productos: state => state.mStock.productos
})
without mapState you'd write:
computed: {
productos() {
return this.$store.state.mStock.productos
}
}

Child component to use parent function in vue js

I have a method initialized within the parent component called setMessage() and I'd like to be able to call it within the child component.
main.js
const messageBoard = new Vue({
el: '#message-board',
render: h => h(App),
})
App (App.vue (parent))..
export default {
data() {
return { messages: state }
},
methods: {
setMessage(message) {
console.log(message);
}
},
template: `
<div>
<child-component></child-component>
</div>
`,
}
child
const child = Vue.extend({
mounted() {
// attempting to use this function from the parent
this.$dispatch('setMessage', 'HEY THIS IS MY MESSAGE!');
}
});
Vue.component('child-component', child);
Right now I'm getting this.$dispatch is not a function error message. What am I doing wrong? How can I make use of parent functions in various child components? I've also tried $emit, it doesn't throw an error & it doesn't hit the function.
Thank you for your help in advance!
You have a couple options.
Option 1 - referencing $parent from child
The simplest is to use this.$parent from your child component. Something like this:
const Child = Vue.extend({
mounted() {
this.$parent.setMessage("child component mounted");
}
})
Option 2 - emitting an event and handling in parent
But that strongly couples the child to its parent. To fix this, you could $emit an event in the child and have the parent handle it. Like this:
const ChildComponent = Vue.extend({
mounted() {
this.$emit("message", "child component mounted (emitted)");
}
})
// in the parent component template
<child-component #message="setMessage"></child-component>
Option 3 - central event bus
One last option, if your components don't have a direct parent-child relationship, is to use a separate Vue instance as a central event bus as described in the Guide. It would look something like this:
const bus = new Vue({});
const ChildComponent = Vue.extend({
mounted() {
bus.$emit("message-bus", "child component mounted (on the bus)");
}
})
const app = new Vue({
...
methods: {
setMessage(message) {
console.log(message);
}
},
created() {
bus.$on('message-bus', this.setMessage)
},
destroyed() {
bus.$off('message-bus', this.setMessage)
}
});
Update (Option 2a) - passing setMessage as a prop
To follow up on your comment, here's how it might work to pass setMessage to the child component as a prop:
const ChildComponent = Vue.extend({
props: ["messageHandler"],
mounted() {
this.messageHandler('from message handler');
}
})
// parent template (note the naming of the prop)
<child-component :message-handler="setMessage"></child-component>
// parent component providing 'foo'
var Provider = {
methods: {
foo() {
console.log('foo');
},
},
provide: {
foo: this.foo,
},
}
// child component injecting 'foo'
var Child = {
inject: ['foo'],
created() {
this.foo() // => "foo";
},
}

Categories

Resources