Search code examples
typescriptvue.jsbootstrap-vuevuejs3vue-composition-api

How to access root context from a composition function in Vue Composition API / Vue 3.0 + TypeScript?


I want to create a reusable wrapper function written in TypeScript for triggering a toast notification by using a composition function, as defined in the current specification for Vue 3.0: Composition API RFC.

This example is using BootstrapVue v2.0 toast component. With Vue 2, it would be invoked via the this.$bvToast Vue component instance injection in the root context:

this.$bvToast.toast('Error happened', {
  title: 'Oh no',
  variant: 'danger'
});

This service-like composition function would look much like this:

// File: @/util/notify.ts
export function useNotify() {
  const notifyError = (title: string, msg: string) => {
    // How to access context.root as in a function component, without passing it to this function?
    context.root.$bvToast.toast(msg, {
      title,
      variant: 'danger'
    });
  };

  return { notifyError};
}

export default useNotify;

And would be used much like this:

// Use in your functional component:
import { createComponent } from '@vue/composition-api';

import { useNotify} from '@/util/notify';

export default createComponent({
  name: 'MyFailingComponent',
  setup() {
    const { notifyError } = useNotify();

    notifyError('Request error', 'There was an error processing your request, please try again later.');

    return {};
  }
});

Solution

  • Well, I soon found out a proper example on that same RFC site. But decided to share my examples here.

    The RFC site doesn't include examples in TypeScript at the moment, for clarity's sake I presume. As this new way of writing Vue 3.0 components and composition functions (as a replacement to Mixins) takes a bit of getting used to.

    Answer: You can pass the context object directly to the composition function when object-destructuring the needed parts into your component code.

    // File: @/util/notify.ts
    // import { SetupContext } from '@vue/composition-api';
    
    export function useNotify({ root }) {
      const notifyError = (title: string, msg: string) => {
        root.$bvToast.toast(msg, {
          title,
          variant: 'danger'
        });
      };
    
      return { notifyError };
    }
    
    export default useNotify;
    
    // Use in your functional component:
    import { createComponent, SetupContext } from '@vue/composition-api';
    
    import { useNotify} from '@/util/notify';
    
    export default createComponent({
      name: 'MyFailingComponent',
      setup(props: any, context: SetupContext) {
        const { notifyError } = useNotify(context);
    
        notifyError('Request error', 'There was an error processing your request, please try again later.');
    
        return {};
      }
    });
    

    Same using TypeScript types with complex object destructuring, when passing several function arguments as an object:

    // File: @/util/notify.ts
    import { SetupContext } from '@vue/composition-api';
    
    export function useNotify({ context, defaultTitle = 'Hey!' }: { context: SetupContext, defaultTitle?: string }) {
      const notifyError = (msg: string, title?: string) => {
        context.root.$bvToast.toast(msg, {
          title: title || defaultTitle,
          variant: 'danger',
        });
      };
    
      return {
        notifyError,
      };
    }
    
    export default useNotify;
    
    
    // Usage like:
    const { notifyError } = useNotify({ context });
    // Or
    const { notifyError } = useNotify({ context, defaultTitle: 'Hey there' });
    

    Neat syntax, well done Vue community!