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

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!


There is also:

import { getCurrentInstance } from 'vue'  // or from '@vue/composition-api'

This will get the calling component's root context from this method.

const root = getCurrentInstance();  // same as ctx.root in component

You might end up passing the context to every composable because their dependencies could require the context themselves.

There is an alternative solution for providing the root instance without the need to pass it to every composable you have. This makes their usage in components a bit easier:

You can create a generic useRoot composable and implement it with Vue's provide/inject feature:

// File: @/components/Root.js
// This is the app root
import useRoot from '@/composables/useRoot'

export default {
  setup(props, context) {
    const { provideRoot } = useRoot()
    provideRoot(context.root)
  }
}
// File: @/composables/useFancyStuff
// This is your composable (no arguments needed!)
import useRoot from '@/composables/useRoot'

export default function useNavigation() {
  const { injectRoot } = useRoot()
  const { $router } = injectRoot() // if you want to use the router

  $router.push('/')
}
// File: @/composables/useRoot
// The implementation
import { provide, inject } from '@vue/composition-api'

const ProviderSymbol = Symbol()

export default function useRoot() {
  const provideRoot = root => provide(ProviderSymbol, root)
  const injectRoot = () => inject(ProviderSymbol)

  return {
    provideRoot,
    injectRoot
  }
}