diff --git a/packages/@react-spectrum/toast/package.json b/packages/@react-spectrum/toast/package.json index 4347b032680..3a0584a5209 100644 --- a/packages/@react-spectrum/toast/package.json +++ b/packages/@react-spectrum/toast/package.json @@ -39,6 +39,7 @@ "@react-aria/focus": "^3.11.0", "@react-aria/i18n": "^3.7.0", "@react-aria/toast": "3.0.0-alpha.1", + "@react-aria/utils": "^3.15.0", "@react-spectrum/button": "^3.12.0", "@react-spectrum/utils": "^3.9.0", "@react-stately/toast": "3.0.0-alpha.1", diff --git a/packages/@react-spectrum/toast/src/ToastContainer.tsx b/packages/@react-spectrum/toast/src/ToastContainer.tsx index 4f4c348e5ac..4fe560c0671 100644 --- a/packages/@react-spectrum/toast/src/ToastContainer.tsx +++ b/packages/@react-spectrum/toast/src/ToastContainer.tsx @@ -15,6 +15,7 @@ import React, {ReactElement, ReactNode, useEffect, useRef} from 'react'; import {SpectrumToastValue, Toast} from './Toast'; import {Toaster} from './Toaster'; import {ToastOptions, ToastQueue, useToastQueue} from '@react-stately/toast'; +import {useLayoutEffect} from '@react-aria/utils'; import {useSyncExternalStore} from 'use-sync-external-store/shim/index.js'; export interface SpectrumToastContainerProps extends AriaToastRegionProps {} @@ -48,8 +49,25 @@ export function clearToastQueue() { globalToastQueue = null; } -let toastProviders = new Set(); + +let store = new Set(); +let toastProviders = { + add: (ref) => { + store.add(ref); + emitChange(); + }, + delete: (ref) => { + store.delete(ref); + emitChange(); + }, + values: () => store.values() +}; let subscriptions = new Set<() => void>(); +function emitChange() { + for (let fn of subscriptions) { + fn(); + } +} function subscribe(fn: () => void) { subscriptions.add(fn); return () => subscriptions.delete(fn); @@ -69,13 +87,16 @@ function useActiveToastContainer() { */ export function ToastContainer(props: SpectrumToastContainerProps): ReactElement { // Track all toast provider instances in a set. - // Only the first one will actually render. + // All will render null at first. Once they have been registered, all will re-render + // and the last one will become the active toast provider, the rest will still return null. // We use a ref to do this, since it will have a stable identity // over the lifetime of the component. let ref = useRef(); - toastProviders.add(ref); - // eslint-disable-next-line arrow-body-style + useLayoutEffect(() => { + toastProviders.add(ref); + }, [toastProviders, ref]); + useEffect(() => { return () => { // When this toast provider unmounts, reset all animations so that @@ -84,13 +105,10 @@ export function ToastContainer(props: SpectrumToastContainerProps): ReactElement toast.animation = null; } - // Remove this toast provider, and call subscriptions. + // Remove this toast provider. // This will cause all other instances to re-render, // and the first one to become the new active toast provider. toastProviders.delete(ref); - for (let fn of subscriptions) { - fn(); - } }; }, []);