[Vue warn]: inject() can only be used inside setup() or functional components
Asked Answered
H

1

6

I wanted to use the Toast component from PrimeVue library and I also wanted to create a nice re-usable service for it but I am getting this error. This doesn't seem to be a problem if I don't try to extract a separate service for the Toast notification.

But I do want to call useToast() from inside my custom service and not directly in component's setup function.

I am using Vue 3.2.25 with Vite.js 2.9.9 and the latest version of PrimeVue

[Vue warn]: inject() can only be used inside setup() or functional components.
[Vue warn]: Unhandled error during execution of native event handler 
  at <App>

Uncaught Error: No PrimeVue Toast provided!
    at useToast (usetoast.esm.js:8:15)
    at Proxy.showToast (toastService.js:4:19)
    at _createElementVNode.onClick._cache.<computed>._cache.<computed> (App.vue:4:21)
    at callWithErrorHandling (runtime-core.esm-bundler.js:155:22)
    at callWithAsyncErrorHandling (runtime-core.esm-bundler.js:164:21)
    at HTMLButtonElement.invoker (runtime-dom.esm-bundler.js:369:13)

Here is a CodeSandbox link: https://codesandbox.io/s/prime-vue-toast-issue-owcio8?file=/src/services/toastService.js

Here is my main.js

import App from './App.vue'
import { createApp } from 'vue'
import PrimeVue from 'primevue/config';
import 'primevue/resources/primevue.min.css';
import 'primevue/resources/themes/lara-dark-blue/theme.css';
import ToastService from 'primevue/toastservice';

const app = createApp(App);

app.use(PrimeVue);
app.use(ToastService);

app.mount('#app')

And here is my App.vue

<template>
    <Toast />

    <button @click="showToast">Show toast!</button>
</template>

<script setup>
    import Toast from 'primevue/toast';
    import { showToast } from './services/toastService';
</script>

and here is my toastService.js:

import { useToast } from "primevue/usetoast";

const showToast = () => {
    const toast = useToast();

    toast.add({ severity: 'info', detail:'Hello' });
}

export { showToast }
Heresy answered 29/5, 2022 at 16:14 Comment(0)
F
15

Vue composables are primarily supposed to be used directly in setup function, when component instance is created. Some of them can be used in other places but this depends on the implementation of a composable and should be additionally confirmed.

The error suggests that useToast uses inject internally, this restricts the usage of this composable.

For a reusable service, it can be:

import { useToast } from "primevue/usetoast";

export const useToastService = () => {
  const toast = useToast();

  const showToast = () => {
    toast.add({ severity: 'info', detail:'Hello' });
  }

  return { showToast };
};

And used like:

const { showToast } = useToastService();

Nothing in PrimeVue Toast implementation actually requires to use useToast composable, it's a convenience helper; see this and this. With some risk of refactoring toast service on the next major library update, it could be simplified to using:

import ToastEventBus from 'primevue/toasteventbus';

export const useToastService = () => {
  const showToast = () => {
    ToastEventBus.emit('add', { severity: 'info', detail:'Hello' });
  }

  return { showToast };
};

This way the service could be used with some discretion in any place of the app, e.g. in a router.

Fogdog answered 29/5, 2022 at 16:41 Comment(2)
can you explain your code why it works? I spent 1 day using ChatGPT and didn't succeedBeecham
@Beecham The original implementation injects ToastEventBus with provide/inject that can be only used inside a component, this limitation is unnecessary in this case. The last snippet imports ToastEventBus directly without provide/inject, so it can be used in any place of the appFogdog

© 2022 - 2024 — McMap. All rights reserved.