Skip to content

Commit

Permalink
Simplify KaizenProvider (#5426)
Browse files Browse the repository at this point in the history
* Simplify KaizenProvider

* changeset

* Fix story

* Fix order import

* Revert change to stories and ensure that container is set

* Replicate the issue

* Good ol js to create element

* Revert changes in story

* Refactor

* Update changeset
  • Loading branch information
vietanhtran16 authored Jan 7, 2025
1 parent 05b87e3 commit 7303979
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 29 deletions.
7 changes: 7 additions & 0 deletions .changeset/proud-cameras-lay.md
Original file line number Diff line number Diff line change
@@ -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.
6 changes: 2 additions & 4 deletions packages/components/src/KaizenProvider/KaizenProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,19 @@ export type KaizenProviderProps = {

export const KaizenProvider = ({ children, locale = 'en' }: KaizenProviderProps): JSX.Element => {
const [documentIsAvailable, setDocumentIsAvailable] = useState<boolean>(false)
const [notificationsList, setNotificationsList] = useState<JSX.Element>()

useEffect(() => {
// SSR does not have a document, which is required for ToastNotificationsList.
// Await document render before rendering the component.
if (document !== undefined) {
setNotificationsList(<ToastNotificationsList />)
setDocumentIsAvailable(true)
}
}, [documentIsAvailable])
}, [])

return (
<OptionalIntlProvider locale={locale}>
<ToastNotificationProvider>
{notificationsList}
{documentIsAvailable && <ToastNotificationsList />}
{children}
</ToastNotificationProvider>
<FontDefinitions />
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Element | null>(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(
<div
data-testid="toast-notifications-list"
role="status"
className={styles.toastNotificationsList}
>
<ToastNotificationsMap
notifications={notifications}
onHide={removeToastNotification}
container={containers[0]}
/>
</div>,
document.body,
return toastContainer ? (
<ToastNotificationsMap
notifications={notifications}
onHide={removeToastNotification}
container={toastContainer}
/>
) : (
<></>
)
}

Expand Down

0 comments on commit 7303979

Please sign in to comment.