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 {};
}
});
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
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With