how to make a future builder like widget in Vue js

375
Update August 2020

Now, you can just import PromiseBuilder component from npm.

Vue Promise Builder on NPM


PromiseBuilder.vue

<template>
  <div>
    <slot
      :pending="pending"
      :fulfilled="fulfilled"
      :rejected="rejected"
      :settled="settled"
      :result="result"
      :error="error"
    />

    <slot v-if="pending" name="pending" />
    <slot v-else-if="fulfilled" name="fulfilled" :result="result" />
    <slot v-else-if="rejected" name="rejected" :error="error" />

    <slot v-if="settled" name="settled" :result="result" :error="error" />
  </div>
</template>

<script>
import { reactive, toRefs, watch, computed } from '@vue/composition-api'

const PromiseStatus = Object.freeze({
  pending: Symbol('pending'),
  fulfilled: Symbol('fulfilled'),
  rejected: Symbol('rejected'),
})

export default {
  props: {
    promise: {
      type: Promise,
      required: true,
    },
  },

  setup (props) {
    const state = reactive({
      status: PromiseStatus.pending,
      result: null,
      error: null,
      pending: computed(() => state.status === PromiseStatus.pending),
      fulfilled: computed(() => state.status === PromiseStatus.fulfilled),
      rejected: computed(() => state.status === PromiseStatus.rejected),
      settled: computed(() => state.status !== PromiseStatus.pending),
    })

    watch(
      () => props.promise,
      async (promise) => {
        state.status = PromiseStatus.pending
        state.result = null
        state.error = null

        try {
          state.result = await promise
          state.status = PromiseStatus.fulfilled
        } catch (error) {
          state.error = error
          state.status = PromiseStatus.rejected

          throw error
        }
      },
      {
        immediate: true,
      },
    )

    return {
      ...toRefs(state),
      calculate,
    }
  },
}
</script>

You can use either default slot with props or named slot for each promise state

<template>
  <div>
    <PromiseBuilder #default="snapshot" :promise="calculation">
      <template v-if="snapshot.pending">
        Calculating
      </template>
      <template v-else-if="snapshot.fulfilled">
        {{ snapshot.result }}
      </template>
      <template v-else-if="snapshot.rejected">
        {{ snapshot.error.message }}
      </template>

      <template v-if="snapshot.settled">
        <button @click="calculation = calculate()">
          Retry
        </button>
      </template>
    </PromiseBuilder>

    <PromiseBuilder :promise="calculation">
      <template #pending>
        Calculating
      </template>
      <template #fulfilled="{ result }">
        {{ result }}
      </template>
      <template #rejected="{ error }">
        {{ error.message }}
      </template>
      <template #settled>
        <button @click="calculation = calculate()">
          Retry
        </button>
      </template>
    </PromiseBuilder>
  </div>
</template>

<script>
import { reactive, toRefs } from '@vue/composition-api'

export default {
  setup () {
    const state = reactive({
      calculation: calculate(),
    })

    async function calculate () {
      await new Promise(resolve => setTimeout(resolve, 1000))
      const rejected = sample([true, false])

      if (rejected) {
        throw new Error('error message')
      }

      return Math.floor(Math.random() * 100)
    }

    return {
      ...toRefs(state),
    }
  },
}

function sample (collection) {
  return collection[Math.floor(Math.random() * collection.length)]
}
</script>
Share:
375
Neo Wakeup
Author by

Neo Wakeup

Updated on December 17, 2022

Comments

  • Neo Wakeup
    Neo Wakeup over 1 year

    this is my first Question. I want to know how to make A Component in Vue js. The component will have functionality like that of a FutureBuilder Widget from Flutter Framework.The Component will deal with a Promise in javascript (for e.g a network call or some other promise) and return a component based on the state of the promise.I want to do this because it makes the code look a lot cleaner to ME.

    A Future Builder is like this:-

    FutureBuilder(
      future: _calculation, // a previously-obtained Future<String> or null
      builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
         if (snapshot.hasData) { return AWidgetWithData();}
         else if (snapshot.hasError) {return ErrorWidget();} 
        else { WidgetWhenThereIsNoInternet();}
    

    );

    It takes a Future in dart (or a Promise in javascript) and returns something depending upon the State of the Future(Promise)

    so in Vuejs i want a Component which would achieve similar functionality.

    <PromiseBuilder :promise="axios.get('https://api-endpoint.org')">
      <div slot="hasData">Data Recieved {{data}}</div>
      <div slot="hasError">Error Occured! {{data}}</div> 
      <div slot="none"> Check internet</div></PromiseBuilder>
    

    you can shorten/improve this if you like.