3
votes

I wrote a "loading state" mixin for Vue 2:

export default {
  props: {
    loading: {
      type: Boolean,
      default: false
    },
  },
  data () {
    return {
      innerLoading: false,
    }
  },
  mounted () {
    this.innerLoading = !!this.loading
  },
  methods: {
    startLoading () {
      this.$emit('update:loading', this.innerLoading = true)
    },
    stopLoading () {
      this.$emit('update:loading', this.innerLoading = false)
    },
  },
  computed: {
    isLoading () {
      return !!this.innerLoading
    },
    isNotLoading () {
      return !this.innerLoading
    },
  },
  watch: {
    loading (loading) {
      this.innerLoading = !!loading
    },
  }
}

I use this mixin for other components to hold the loading state. For example for forms, buttons, tables etc.

Now, Im trying to rewrite this mixin to composition API style for Vue 3. Ideally, I would like to use my loading composable like this:

// components/Table.vue

import 'useLoading' from 'src/composables/loading'

export default defineComponent({
  setup () {
    const { startLoading, stopLoading, innerLoading } = useLoading()

    // ...
    
    return { startLoading, stopLoading, innerLoading, ... }
  }
})

My question:

// How can I define the loading prop inside the setup() function?
props: {
  loading: {
    type: Boolean,
    default: false
  },
},

Of course I can define my component like this:

import 'useLoading' from 'src/composables/loading'

export default defineComponent({
  props: {
    loading: {
      type: Boolean,
      default: false
    },
  },
  setup () {
    const { startLoading, stopLoading, innerLoading } = useLoading();
  }
})

But imagine, I have 20 components using this mixin/composable. So I want to define that loading prop only ONCE (like I did in mixin).

Is there a way how to do it with composition API?

2
@mspiderv props can't be declared inside setup(). Since setup() itself receives the props values as its second argument, props would have to be declared before setup() is even invoked. Currently, the only way to declare props is using the Options API.tony19
How did you finally implement this? How did you get access to loading prop in the watcher within the composable?Donkarnash
@Donkarnash I implement it by creating two functions: useLoading and withLoading. The useLoading contains the whole logic and withLoading returns props exactly like @Daniel said (stackoverflow.com/a/66604160/2987610). I don't use watcher in my new implementation, but't it seems to me like you are asking something else.mspiderv
Thanks for response. in useLoading where you have all the logic how do you access the props of component which is using the useLoading composable? I can understand that in the consuming component you include ...withLoading() to declare the prop loading but then how do you get access to the prop in useLoading?Donkarnash
@Donkarnash Like this: export function useLoading (props, ctx) { props.loading; } But of course, you need to pass props and ctx to useLoading like this: setup (props, ctx) { const { ... } = useLoading(props, ctx); return [ ... ] } Another syntax is this: setup(props, ctx) { return [ ...useLoading(props, ctx) ] } or this: setup: useLoadingmspiderv

2 Answers

10
votes

you may be able to do something like this

import {withProps, useLoading} from "src/composables/loading";

export default defineComponent({
  props: {
    ...withProps()
  },
  setup () {
    const { startLoading, stopLoading, innerLoading } = useLoading();
  }
})

where withProps is a function that would have your definitions

export const withProps = () => ({
  loading: {
    type: Boolean,
    default: false
  },
})

of course it doesn't need to be a function, but in some cases it may be helpful and preemptively making it a function can make api consistent.

3
votes

You could use a mixin called loadable that has the loading prop :

export default{
 props: {
  loading: {
    type: Boolean,
    default: false
  },
},
}

then import it inside your component and add it to the mixins option :

import useLoading from 'src/composables/loading'
import loadable from 'src/mixins/loadable'

export default defineComponent({
 mixins:[loadable],// you could also add other mixins that contain other props like colorable
  setup (props) {
    // here you could use props.loading
    const { startLoading, stopLoading, innerLoading } = useLoading();
  }

Note : a mixin could define other options like data and computed, if you want only to use the composition API the mixins should define only the props to make your code consistent.

If you don't want to use mixins you could check this example