posva/vue-promised

Global defaults for pending and error

Opened this issue · 14 comments

posva commented

In big applications, it is very common to use similar if not the same pending and error handler. This would prevent a lot of boilerplate from users

👍 Yup, this would be very useful!

How about making the render function like this?

render (h) {
  if (this.error instanceof Error || (this.error && this.error.length)) {
    if (this.$scopedSlots && this.$scopedSlots.catch) {
      return this.$scopedSlots.catch(this.error)
    } else if (this.$options.components.Catch) {
      return h('catch', {props: this.$attrs})
    } else {
      throw new Error('Provide exactly one scoped slot named "catch" for the rejected promise')
    }
  } else if (this.resolved) {
    const slot = this.$scopedSlots.default || this.$scopedSlots.then
    assert(
      this.$scopedSlots && slot,
      'Provide exactly one default/then scoped slot for the resolved promise'
    )
    return slot.call(this, this.data)
  } else if (this.isDelayElapsed) {
    if (this.$slots.default && this.$slots.default.length === 1 || this.$slots.pending && this.$slots.pending.length === 1) {
      return this.$slots.default ? this.$slots.default[0] : this.$slots.pending[0]
    } else if (this.$options.components.Pending) {
       return h('pending', {props: this.$attrs})
    } else {
      throw new Error('Provide exactly one default/pending slot with no `slot-scope` for the pending promise')
    }      
  }

  // do not render anything
  return h()
}

Then a user could extend the component with default pending/catch like this:

import Promised from 'vue-promised'
import Pending from './Pending.vue'
import Catch from './Catch.vue'
export default {
  extends: Promised,
  components: {Pending, Catch}
}

And it would also let the user pass props through Promised into their Pending/Catch components.

posva commented

It's similar, except I was thinking about hoisting the Catch and Error component with some conventions in the lib file and use them as fallbacks. Passing a prop for the component also looks like a good thing as it is possible to have different kind of errors or loading components but at the same time I feel like duplicating the API for a ver small syntax gain that is chained to a convention on the error prop

Anyway it'd be nice to be able to do something like this:

<promised spinner="large">

Or maybe if you passed the props like h('Pending', {props:this.pending})
Then the user could do something like:

<promised :pending="{size:'large', message:'Loading posts'}">
posva commented

But instead you can write:

<Promised>
  <Spinner slot="pending" size="large"/>
</Promised>

which is explicit and doesn't required a global component definition with the name Spinner. You may also have different loading components so having one single spinner wouldn't be enough

Hmm it wouldn't require a global definition, the user would just extend the component like in my previous example, and then pass some props through Promised into their Pending/Catch components when they need to be customized (so the Pending component would receive the prop spinner:'large').

posva commented

yeah but having all that boilerplates completely defeats the initial purpose 😄

I don't understand... You created this issue yourself, and you wrote this:

In big applications, it is very common to use similar if not the same pending and error handler

The only solutions that I can see is to either make the component extendable like I suggested, or provide a factory function that accepts Pending/Catch components as arguments, which is basically equivalent. And when you do that you probably want to give the user the option to sometimes pass props to tweak their default spinner/error display.

Or how would you do it?

posva commented

yes, with some functions that can be called and props to pass down props to the component if none is provided

Hmm, but how is it too much boilerplate? The user imports Promised, extends it, and then exports it, and in the rest of the app they import this extended component. That (plus creating the Pending and Catch components) seems like the minimum to me.

Anyway, I actually switched from Promises to a similar but synchronous class, since then/await doesn't mix well with computed properties/getters.

Hey man, I've used your package for a long time now (it's great) and the way I do it now is wrapping vue promised inside my own component which has a default loader (but can be changed by a prop) and a default error state.

I have some thoughts on making this easier being a package though, how about you make a plugin out of it instead of a component, that way you can pass options and do something like this:

import { Promised } from 'vue-promised';
import LoadingComponent from '';
import ErrorComponent from '';

Vue.use(Promised, {
  id: 'vue-promised', // Promised by default?
  defaults: {
    pending: LoadingComponent,
    catch: ErrorComponent,
  },
});

The plugin registers the component, the LoadingComponent and ErrorComponent both receive the slot-scope as props.

posva commented

Hello, I'm glad you like it. It won't be a plugin, I want to keep the flexibility of a component. The registration will probably be done through an exported function or by setting a property in the component option object

@posva Alright, well I wouldn't really know how to abstract away the complexities of that, but I'm keen to see how you'll solve it :).

Good luck, looking forward to your solution!

status