primefaces / primevue

Next Generation Vue UI Component Library
https://primevue.org
MIT License
9.89k stars 1.19k forks source link

Toast, ToastService - unable to use useToast() composable outside of SFC #6403

Open dcb99 opened 1 week ago

dcb99 commented 1 week ago

Describe the bug

Currently unable to use the useToast() composable in a module. Attempting to do so results in

[Vue warn]: inject() can only be used inside setup() or functional components. [chunk-2LTNOSJU.js:1517:13](https://primevue3vitetsissuetemplate-gxzs--5173--28a8ce1b.local-corp.webcontainer.io/node_modules/.vite/deps/chunk-2LTNOSJU.js?v=bcc9c28a)
[Vue warn]: Unhandled error during execution of native event handler 
  at <Button label="Button" onClick=fn > 
  at <App> [chunk-2LTNOSJU.js:1517:13](https://primevue3vitetsissuetemplate-gxzs--5173--28a8ce1b.local-corp.webcontainer.io/node_modules/.vite/deps/chunk-2LTNOSJU.js?v=bcc9c28a)
Uncaught Error: No PrimeVue Toast provided!
    useToast chunk-TRTEGWUV.js:10
    infoToast toasts.ts:4
    0 App.vue:6
    callWithErrorHandling chunk-2LTNOSJU.js:1663
    callWithAsyncErrorHandling chunk-2LTNOSJU.js:1670
    invoker chunk-2LTNOSJU.js:10305

Reproducer

https://stackblitz.com/edit/primevue-3-vite-ts-issue-template-97zpyl?file=src%2FApp.vue

PrimeVue version

3.53

Vue version

3.x

Language

TypeScript

Build / Runtime

Vite

Browser(s)

No response

Steps to reproduce the behavior

Click on the Button. You'll see the error messages in the console

Expected behavior

Using the useToast composable outside of SFC should be supported and work without errors.

mkalinski93 commented 23 hours ago

same here. It is not possible to call toasts in service modules for example or other areas than the setup call

*Edit:

one possible "quick" solution is to add an eventbus. You can emit messages to a receiver that is a component inside your app.vue. If a message is received, it will use the useToaster composables. That way, it should be fine.

MessageService.vue

<script setup lang="ts">
import type { NotificationOptions } from '~/types/misc'
import { ToastMessageRequested } from '~/types/events'

const toaster = useToast()
useEventBus<NotificationOptions>(ToastMessageRequested).on((message) => {
  toaster.add({
    detail: message.title,
    summary: message.message,
    severity: '',
    kind: message.kind,
    group: 'notification-base',
    closable: message.persist || message.closable,
    life: message.persist ? undefined : message.duration,
  })
})
</script>

<template>
  <Toast />
</template>

useNotification.ts

export const useNotification = () => {
  const createNotification = (opts: NotificationOptions) => {
    const defaultOptions: NotificationOptions = {
      title: '',
      message: '',
      duration: 3000,
      persist: false,
      closable: false,
      kind: 'info',
    }

    const options = { ...defaultOptions, ...opts }
    useEventBus(ToastMessageRequested).emit(options)
  }
  const info = (title: string, message: string, opts?: NotificationOptions) => createNotification({ title, message, kind: 'info', ...opts })
  const success = (title: string, message: string, opts?: NotificationOptions) => createNotification({ title, message, kind: 'success', ...opts })
  const warning = (title: string, message: string, opts?: NotificationOptions) => createNotification({ title, message, kind: 'warning', ...opts })
  const error = (title: string, message: string, opts?: NotificationOptions) => createNotification({ title, message, kind: 'error', ...opts })

  return {
    info,
    success,
    warning,
    error,
  }
}
dcb99 commented 20 hours ago

same here. It is not possible to call toasts in service modules for example or other areas than the setup call

*Edit:

one possible "quick" solution is to add an eventbus. You can emit messages to a receiver that is a component inside your app.vue. If a message is received, it will use the useToaster composables. That way, it should be fine.

MessageService.vue

<script setup lang="ts">
import type { NotificationOptions } from '~/types/misc'
import { ToastMessageRequested } from '~/types/events'

const toaster = useToast()
useEventBus<NotificationOptions>(ToastMessageRequested).on((message) => {
  toaster.add({
    detail: message.title,
    summary: message.message,
    severity: '',
    kind: message.kind,
    group: 'notification-base',
    closable: message.persist || message.closable,
    life: message.persist ? undefined : message.duration,
  })
})
</script>

<template>
  <Toast />
</template>

useNotification.ts

export const useNotification = () => {
  const createNotification = (opts: NotificationOptions) => {
    const defaultOptions: NotificationOptions = {
      title: '',
      message: '',
      duration: 3000,
      persist: false,
      closable: false,
      kind: 'info',
    }

    const options = { ...defaultOptions, ...opts }
    useEventBus(ToastMessageRequested).emit(options)
  }
  const info = (title: string, message: string, opts?: NotificationOptions) => createNotification({ title, message, kind: 'info', ...opts })
  const success = (title: string, message: string, opts?: NotificationOptions) => createNotification({ title, message, kind: 'success', ...opts })
  const warning = (title: string, message: string, opts?: NotificationOptions) => createNotification({ title, message, kind: 'warning', ...opts })
  const error = (title: string, message: string, opts?: NotificationOptions) => createNotification({ title, message, kind: 'error', ...opts })

  return {
    info,
    success,
    warning,
    error,
  }
}

I'd much rather have PrimeVue work as expected instead of having to resort to hacky workarounds. The problem with the useToast composable also breaks component tests that call useToast image