Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vue waiting for data from props before using

I keep running into an issue where I am awaiting data from a prop to do something within it inside my created hook. It keeps throwing errors such as "this.value is not defined" (As it has not yet loaded from prop)

I have fixed some of these issues using v-if and only using as needed. However in some cases I wanted to use that prop info within my created and when that happens, I end up getting errors and am not sure how to best tell that part of the created to "wait" for the data before running.

Any advice?

--

Update to add some example code.

On the App.js page, I am calling in the "userInfo" and setting it in data like so. I am doing this for two reasons. First to check if user is logged in, and secondly taking the time to save the users info as it will be used later on throughout the app. (Please note, user and userInfo are different and contain different info. User is email/password whilst userInfo is username, bio, etc.)

data() {
  return {
    user: null,
    userInfo: null,
  }
},
created(){
//let user = firebase.auth().currentUser
  firebase.auth().onAuthStateChanged((user) => {
    if(user){
      this.user = user
      let ref = db.collection('users')
      .where('user_id', '==', this.user.uid)
      ref.get().then(snapshot => {
      snapshot.forEach(doc => {
        this.userInfo = doc.data()
      })
    })

    } else {
      this.user = null
    }
  })
},

Now, I pass this information down within the using v-bind. and then call it in areas I need some info using props.

The issue comes when I want to do something such as -

if(this.userInfo) {
  value = true  
}

Calling this.userInfo throws an error "this.userInfo is null" (I set it to null in the data() of the page as default).

like image 454
KJParker Avatar asked Jan 22 '20 10:01

KJParker


4 Answers

As already mentioned in the comments, you're running into a Promise resolving issue. When passing a result as a Prop which gets resolved by a Promise, that result will basically be empty which eventually will lead to an error in your Child Components created() hook.

I would recommend using v-if as you've already done in the Child Component itself. However, I'd use the v-if in the Parent Component to simply show/hide the Child. That should not only resolve the problem within your created() hook, but also help building up a more readable markup in your Child Component where you could just remove all v-ifs.

So let's assume you have a Parent/Child relationship like that.

<SomeParentComponent>
  <SomeChildComponent :someProp="yourProp" />
</SomeParentComponent>

When yourProp is set after a resolving Promise, the initial value will be null, undefined, or whatever you've set it to. To avoid passing in an empty prop you can just wrap the in a v-if which's expression evaluates to yourProp just like so.

<SomeParentComponent>
  <SomeChildComponent :someProp="yourProp" v-if="yourProp" />
</SomeParentComponent>                           <!-- ^-- See here -->

Doing so, the Child will only be rendered when yourProp is set and therefore you won't get any errors anymore.

like image 124
Aer0 Avatar answered Oct 10 '22 17:10

Aer0


The accepted answer provides a great solution, which set v-if on entire Child component in Parent component. However there's still some edge cases where your prop literally becomes empty or null if the API returns so, which eventually ends up never rendering your Child component at all though you might need to show some default texts/styles, or maybe you're working on some projects that you don't have control over the parent component. In those cases, you could internally handle this problem just fine by checking if the prop has already available in the created hook, if not, you should dynamically watch this prop until it becomes available.

// your component
export default {
  props: ['userInfo'],

  created() {
    // check if the prop userInfo available or not
    if (this.userInfo) {
      // do your thing
    } else {
      // start watching this prop
      const unwatch = this.$watch('userInfo', () => {
        // do your thing when userInfo is available

        // tear down this watcher as you now no longer need it
        unwatch()
      })
    }
  }
}
like image 28
KienHT Avatar answered Oct 10 '22 17:10

KienHT


Props should be available in the created hook. Assuming the values are synchronous. Try to add default values, in order to remove unnecessary v-if directives. This should solve the issue for both synchronous and asynchronous data.

{
  props: {
    length: {
      type: Number,
      default: 0,
    },
  },
}
like image 41
chr Avatar answered Oct 10 '22 17:10

chr


There are at least two approaches I can think of:

1. Dependency injection

The parent/root to provide the data/object down the component tree and the receiving end (children) to inject them.

Parent

{
  data: () => ({
    userInfo: null
  }),

  created() {
    // Populates data via Firebase 
    // Code omitted for brevity...

    this.userInfo = doc.data();
  },

  provide() {
    return Object.defineProperty({}, 'userInfo', {
      get: () => this.userInfo
    })
  }
}

Children

// Children
{
  inject: ['userInfo'],

  created() {
    if (this.userInfo) {
      // Do something 
    }
  },
  mounted() {
    if (this.userInfo) {
      // Do something 
    }
  },
  // Any lifecycle hooks, methods or virtually anywhere needing a reference to this object
}

2. Vuex store

Rather than passing props and having to deal with possible asynchrony issues; you could let Vuex take care of synchronizing the states for you and stop worrying about the data being null or undefined.

Store

export default {
  state: {
    userInfo: null
  },
  mutations: {
    UPDATE_USER(state, payload) {
      state.userInfo = payload;
    }
  }
}

Parent/App.js

export default {
  created() {
    // On firebase success callback
    this.$store.commit('UPDATE_USER', doc.data());
  }
}

Children

import { mapState } from 'vuex';

export default {
  computed: {
    ...mapState([
      'userInfo'
    ])
  },

  created() { },
  mounted() { },
  methods() { 
    if (this.userInfo) {
      // Do something safely
    }
  },
  // Anywhere
}
like image 20
Yom T. Avatar answered Oct 10 '22 17:10

Yom T.