Code Monkey home page Code Monkey logo

vue-loadable's Introduction

vue-loadable

Build Status License Library minified size Library minified + gzipped size FOSSA Status

vue-loadable improves your loading state flow by providing methods and helpers to manage it.

<template>
  <p v-if="$isLoading('initialize')">Initializing...</p>
  <div v-else>
    <!-- Loaded content... -->
  </div>
</template>

<script>
import { mapActions } from 'vuex';
import { loadable, mapLoadableMethods } from 'vue-loadable';

export default {
  ...,

  methods: {
    async initialize () {
      // Set initialize state as loading on async function start.
      this.$setLoading('initialize');

      try {
        await this.$store.dispatch('users/fetchUsers');
      } catch (_) {}

      // Unset initialize state as loading on async function end.
      this.$unsetLoading('initialize');
    },

    // `loadable` decorator can automate this process.
    initialize: loadable(async function () {
      await this.$store.dispatch('users/fetchUsers');
    }, 'initialize'),

    // An there's `mapLoadableMethods` to map methods into loadable methods (works with Vuex too).
    ...mapLoadableMethods(
      mapActions('users', {
        initialize: 'fetchUsers'
      })
    )
  },
  mounted () {
    this.initialize();
  }
};

Installation

This library is published in the NPM registry and can be installed using any compatible package manager.

npm install --save vue-loadable

# Use the command below if you're using Yarn.
yarn add vue-loadable

Installation from CDN

This module has an UMD bundle available through JSDelivr and Unpkg CDNs.

<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-loadable"></script>

<script>
  // module will be available through `VueLoadable` object.
  console.log(VueLoadable);

  Vue.use(VueLoadable);
</script>

Installation on Vue

vue-loadable need to be installed to enable loadable methods, loadable decorator and mapLoadableMethods helper.

To install globally, just pass default exported object as argment to Vue.use.

import Vue from 'vue';
import Loadable from 'vue-loadable';

Vue.use(Loadable);

You can install it locally instead with LoadableMixin mixin.

<script>
import { LoadableMixin } from 'vue-loadable';

export default {
  mixins: [LoadableMixin],
};
</script>

API

  • loadable decorates a function to change loading state during its execution. It sets the state as loading when function inits and unsets when it throws an error, when it resolves or when it returns a value.

    Second argument is the loading state name, is "unknown" when it's not defined.

    Vue.component('SignInForm', {
      methods: {
        signIn: loadable(async function(name) {
          // ...
        }, 'signIn'),
      },
    
      async mounted() {
        this.$isLoading('signIn');
        //=> false
    
        const promise = this.signIn('Vitor');
    
        this.$isLoading('signIn');
        //=> true
    
        await promise;
    
        this.$isLoading('signIn');
        //=> false
      },
    });

    It passes down the function arguments, rejects the errors and resolves the returned value.

    async function confirmUsername(username: string): Promise<boolean> {
      // ...
    }
    
    export default {
      methods: {
        // Returns a function with same signature, but handling loading states.
        confirm: loadable(confirmUsername, 'confirmation'),
      },
      async mounted(): Promise<void> {
        try {
          const isConfirmed = await this.confirm('VitorLuizC');
          this.$router.push(isConfirmed ? '/checkout' : '/confirmation');
        } catch (error) {
          new Rollbar.Error(error).send();
        }
      },
    };
    TypeScript type definitions.
    type Method =
      | ((...args: any[]) => any)
      | ((this: Vue, ...args: any[]) => any);
    
    type LoadableMethod<T extends Method> = (
      this: Vue,
      ...args: Parameters<T>
    ) => ReturnType<T> extends Promise<any>
      ? ReturnType<T>
      : Promise<ReturnType<T>>;
    
    const loadable: <T extends Method>(
      method: T,
      state?: string,
    ) => LoadableMethod<T>;
  • mapLoadableMethods maps methods into loadable ones that triggers loading states, it works pretty well with Vuex.

    It uses method's names as loading state name.

    <template>
      <div v-if="$isLoading('signInUser')">
        Carregando...
      </div>
      <div v-else>
        <SignedUserArea />
      </div>
    </template>
    
    <script>
    import { mapActions } from 'vuex';
    import { mapLoadableMethods } from 'vue-loadable';
    
    export default {
      methods: mapLoadableMethods(
        mapActions([
          'signInUser',
          'signUpUser'
        ])
      )
    };
    TypeScript type definitions.
    // `Method` and `LoadableMethod` are defined at `loadable` type definitions.
    
    type Methods = Record<string, Method>;
    
    type LoadableMethods<T extends Methods> = {
      [K in keyof T]: LoadableMethod<T[K]>;
    };
    
    const mapLoadableMethods: <T extends Methods>>(
      methods: T,
    ) => LoadableMethods<T>;
  • $isLoading is a method to check if a state is loading.

    Argument is the loading state name, is "unknown" when it's not defined.

    <template>
      <v-spinner v-if="$isLoading('initialize')" />
      <sign-in-form v-else @click="onClickSignIn" ... />
    </template>
    
    <script>
    // ...
    
    export default {
      methods: {
        ...,
        onClickSignIn () {
          if (!this.$isLoading('signIn')) // Call `signIn` only if its not loading.
            return;
    
          this.signIn();
        }
      }
    };
    TypeScript type definitions.
    interface Vue {
      $isLoading(state?: string): boolean;
    }
  • $isLoadingAny is a method to check if any state is loading.

    <template>
      <v-spinner v-if="$isLoadingAny()" />
      <div>
        <sign-in-or-sign-up-form @signIn="onSignIn" @signUp="onSignUp" />
      </div>
    </template>
    
    <script>
    // ...
    
    export default {
      methods: {
        ...,
        onSignUp () {
          if (!this.$isLoadingAny())
            return;
    
          this.signUp();
        },
        onSignIn () {
          if (!this.$isLoadingAny())
            return;
    
          this.signIn();
        }
      }
    };
    TypeScript type definitions.
    interface Vue {
      $isLoadingAny(): boolean;
    }
  • $setLoading is a method to set state as loading.

    Argument is the loading state name, is "unknown" when it's not defined.

    export default {
      methods: {
        ...,
        async onSubmit () {
          this.$setLoading('submission'); // set submission state as loading.
    
          await services.submit(this.fields);
        }
      }
    };
    TypeScript type definitions.
    interface Vue {
      $setLoading(state?: string): void;
    }
  • $unsetLoading is a method to unset state as loading.

    Argument is the loading state name, is "unknown" when it's not defined.

    export default {
      methods: {
        ...,
        async onSubmit () {
          try {
            await services.submit(this.fields);
          } catch {}
    
          this.$unsetLoading('submission'); // unset submission state as loading.
        }
      }
    };
    TypeScript type definitions.
    interface Vue {
      $unsetLoading(state?: string): void;
    }

License

Released under MIT License.

FOSSA Status

vue-loadable's People

Contributors

fossabot avatar vitorluizc avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

Forkers

fossabot

vue-loadable's Issues

Error: Can't use "mapLoadableActions" without Vuex.

In main.js:

import Vue from 'vue';
import Vuex from 'vuex';
import Loadable from 'vue-loadable';
import store from './store';

Vue.use(Vuex);
Vue.use(Loadable);

new Vue({
  store,
  render: h => h(App),
}).$mount('#app');

In RefiningDialog.vue:

import { mapLoadableActions } from 'vue-loadable';

export default {
  methods: {
    ...mapLoadableActions('Refining', [
      'newRefiningTask',
    ]),
  },
};

However, on loading page, I get: Error: Can't use "mapLoadableActions" without Vuex..

I dug into vue-loadable.mjs, and adding import Vuex from 'vuex'; to the top, just under the import Vue line, makes it all work as expected (and it works a treat, really handy solution!), but mucking about in the dist folder in node_modules can't be the intended solution. =)

I feel like I'm missing something in how I'm setting this all up, but even comparing it to examples in the repo, it appears to be correct. There aren't any tests against mapLoadableActions, so I'm kind of at a loss from where to look next.

Ideally I'm just overlooking something, but even still, I'd be interested in submitting a PR with some extra documentation to help out folks who run into troubles like myself.

Should this work between components?

I have two components, both of which have the following code:

<template>
    <div v-if="$isLoading('updatePurchase')">
      Updating...
    </div>
</template>

<script>
...
  methods: {
    ...mapLoadableMethods(
      mapActions('Licenses', [
        'updatePurchase',
      ]),
    ),
  },
...
</script>

If I call this.updatePurchase() from component A, "Updating..." appears on component A, but not component B. If I call this.updatePurchase() from component B, "Updating..." appears on component B, but not component A.

Is this intended behaviour that I have to work around for sharing loading state between multiple components, or have I set something up incorrectly?

Add mapLoadingActions helper to map actions with a state from store

<template>
  <h1 v-if="$loadable.isFetching">
    Loading ...
  </h1>
  <h1 v-else>
    Carregado!
  </h1>
</template>

<script>
  import * as types from '@/store/types'
  import { mapLoadableActions } from 'vue-loadable'

  export default {
    methods: mapLoadableActions({
      load: {
        state: `isLoading`,
        action: types.SEARCH_COMPANY_CLEAN
      }, // async function load () {
         //   const isDefined = 'isLoading' in this.$loading
         //   if (isDefined)
         //     Vue.set(this.$loading, 'isLoading', true)
         //   else
         //     this.$loading.isLoading = true
         //   const result = await this.$store.dispatch(types.SEARCH_COMPANY_CLEAN)
         //   this.$loading.isLoading = false
         //   return result
         // }
      fetch: {
        state: 'isFetching',
        action: types.COMPANY_LIST_PAGINATE
      }
    })
  }
</script>

Add decorator to handle any loadable method

  • Handle any loadable with same function;
  • Exports loadable decorator.

Usage example

import { loadable } from 'vue-loadable';

...

export default {
  ...,
  methods: {
    updateTokens: loadable(async function (tokens) {
      await this.$store.dispatch(types.TOKENS_UPDATE, tokens);
    }),
    createToken: loadable(function (token) {
      return this.$store.dispatch(types.TOKENS_CREATE, token);
    })
  }
}

Any interest in updating to Vue 3?

This is one of my favourite Vue plugins, really makes it a lot easier to display custom loading states for multiple elements, and I'd be sad if I had to leave it behind for Vue 3.

I tried to take a stab at it myself, but I'm really slowed down by my lack of Typescript knowledge, so I imagine it might be much quicker if done by, say, someone who knew Typescript.

So far, I've changed it so that LoadableMixin returns an object, not a Vue.extend(), and changed install to work like:

export function install(app: VueConstructor): void {
  app.mixin(LoadableMixin);
}

However, the way I want to use it is using Vue3's composition API, like so:

const createProject = loadable((values) => store.dispatch('Projects/createProject', values));

The problem I'm running into is that loadable relies on this, and the context of the current component isn't passed in there. I think it probably requires some clever .bind() calls, though I'm not sure where and this is where my lack of TS knowledge is really hampering me.

Support multiple loading states

My components behave different with different loading states. So, this module should support multiple loading states.

  • Add firstLoading state;
  • Add anyLoading state;
  • Add updateLoading state;
  • Add support for custom loading states for any component with different loading states behavior

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.