diff --git a/.changeset/proud-cameras-lay.md b/.changeset/proud-cameras-lay.md new file mode 100644 index 00000000000..d73bc1d47a2 --- /dev/null +++ b/.changeset/proud-cameras-lay.md @@ -0,0 +1,7 @@ +--- +'@kaizen/components': patch +--- + +Support having multiple KaizenProvider on the same page. + +This is helpful when publishing a package which relies toast notification - setting up its own KaizenProvider (which also set up ToastNofitification) will ensure that we won't run into `useToastNotificationContext must be used within the ToastNotificationContext.Provider` as this relies on the consumers' applications having the exact `@kaizen/components` version and depends on how package manager resolves peer deps. Especially for [pnpm](https://pnpm.io/how-peers-are-resolved), it might end up with 2 copies of the exact same version of kaizen/components therefore won't be able to find the `ToastNotificationContext.Provider` set up by the application. diff --git a/packages/components/src/KaizenProvider/KaizenProvider.tsx b/packages/components/src/KaizenProvider/KaizenProvider.tsx index 3f178d76aa3..f8cbf0f2b22 100644 --- a/packages/components/src/KaizenProvider/KaizenProvider.tsx +++ b/packages/components/src/KaizenProvider/KaizenProvider.tsx @@ -11,21 +11,19 @@ export type KaizenProviderProps = { export const KaizenProvider = ({ children, locale = 'en' }: KaizenProviderProps): JSX.Element => { const [documentIsAvailable, setDocumentIsAvailable] = useState(false) - const [notificationsList, setNotificationsList] = useState() useEffect(() => { // SSR does not have a document, which is required for ToastNotificationsList. // Await document render before rendering the component. if (document !== undefined) { - setNotificationsList() setDocumentIsAvailable(true) } - }, [documentIsAvailable]) + }, []) return ( - {notificationsList} + {documentIsAvailable && } {children} diff --git a/packages/components/src/Notification/ToastNotification/ToastNotificationsList/ToastNotificationsList.tsx b/packages/components/src/Notification/ToastNotification/ToastNotificationsList/ToastNotificationsList.tsx index b23065b2b07..95bf6ff450a 100644 --- a/packages/components/src/Notification/ToastNotification/ToastNotificationsList/ToastNotificationsList.tsx +++ b/packages/components/src/Notification/ToastNotification/ToastNotificationsList/ToastNotificationsList.tsx @@ -1,36 +1,35 @@ -import React from 'react' -import { createPortal } from 'react-dom' +import React, { useEffect, useState } from 'react' import { useToastNotificationContext } from '../context/ToastNotificationContext' import { ToastNotificationsMap } from './subcomponents/ToastNotificationsMap' import styles from './ToastNotificationsList.module.scss' +const toastNotificationListId = 'toast-notifications-list' + export const ToastNotificationsList = (): JSX.Element => { const { notifications, removeToastNotification } = useToastNotificationContext() + const [toastContainer, setToastContainer] = useState(null) - const containers = document.querySelectorAll('[data-testid="toast-notifications-list"') - - if (containers) { - // Remove any duplicate instances - // (eg. Storybook docs page has multiple stories each with their own context) - containers.forEach((c, i) => { - if (i === 0) return - c.remove() - }) - } + useEffect(() => { + // this is to ensure that the container is created only once. Regardless of how many KaizenProvider is set up, they will also reuse the same container. + let container = document.querySelector(`[id="${toastNotificationListId}"]`) + if (!container) { + container = document.createElement('div') + container.setAttribute('id', toastNotificationListId) + container.setAttribute('role', 'status') + container.className = styles.toastNotificationsList + document.body.appendChild(container) + } + setToastContainer(container) + }, []) - return createPortal( -
- -
, - document.body, + return toastContainer ? ( + + ) : ( + <> ) }