Search code examples
vue.jsvue-componenthoisting

Vue.js - using functions in component methods


I have a vue2 component, where some methods are pretty long with a lot of callbacks and I would like to structurize it better. I try to follow the guidelines as per callbackhell.com but things don't look that easy within vue method.

Below is the sample of where I am at the moment, it works, but I am confused about the following:

  1. Function hoisting doesn't seem to work here, I need to define my functions first before I run them otherwise, it triggers an error. Why is that, am I missing something here?

  2. When I define inner functions like this, I lose the "this" bound to Vue component in them. I fix it by the .bind(this) after the function definition, it works but looks rather obscure. Is there any better way to define a utility function within methods while still keeping the context of "this"?

  3. Is this generally adding functions into method a good approach in Vue? I know I could make them sibling methods directly in methods: {} object and that resolves most of my issues, but as they are only relevant to the saveFavourites() method and not to anything else in the component, wrapping them within looks cleaner to me?

Thanks so much

methods: {
    saveFavourites() {
        var promptName = function() {
            return this.$swal({
                text: 'Enter name'),
                content: 'input',
                button: {
                    text: 'Save'),
                    closeModal: true,
                },
            })
        }.bind(this);

        var storePlan = function(name) {
            if(!name || (name = name.trim()) === '' ) return;
            axios.post('/api/user-store-daily-favourites', { meals: this.mealIds, name: name })
                .then(response => {
                    if(response.data.status === 'success') {
                        this.$emit('dailyFavouritesUpdated');
                    }
                });
        }.bind(this);

        // running it - if I move this above functions definitions, I get error
        promptName()
            .then( (name) => storePlan(name) );
    },

Solution

  • For question 1, hoisting only applies to function declarations, not function expressions. Compare:

    // Expression
    var promptName = function() {
    

    to:

    // Declaration
    function promptName() {
    

    For further reading see:

    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function
    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function

    For question 2, using bind is fine but you may find it easier to use arrow functions instead, which preserves the surrounding this value.

    var promptName = () => {
      return this.$swal({
        ...
      })
    }
    

    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions

    For question 3, that seems like a matter of personal preference but generally I would move them to separate Vue methods. You have the usual trade offs to consider. Perhaps you feel the indirection is too high a price to pay and you'd rather keep all the code in one place. That's fine.

    In this specific case I suggest you look into async/await instead of using all those extra functions. However, a word of caution, make sure you understand promises well before trying to use async/await as most problems with async/await come from not understanding the underlying mechanism used to implement it.

    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await

    The async/await version would look something like this:

    async saveFavourites() {
      const name = await this.$swal({
        text: 'Enter name',
        content: 'input',
        button: {
          text: 'Save',
          closeModal: true,
        },
      });
    
      if(!name || (name = name.trim()) === '' ) return;
    
      const response = await axios.post('/api/user-store-daily-favourites', {
        meals: this.mealIds,
        name: name
      });
    
      if(response.data.status === 'success') {
        this.$emit('dailyFavouritesUpdated');
      }
    }