Repository navigation
context notification
Manages all application toast notifications and provides the global event pipeline that components use to publish and subscribe to notification events.
The provider renders a single ToastNotification (Carbon Design System) at the top of the component tree and drives it through a queue, preventing duplicate entries and coordinating slide-in/slide-out animations automatically.
Source files:
- src/context/notification/NotificationProvider.tsx
- src/context/notification/NotificationContext.tsx
- src/context/notification/eventBridge.ts
- src/hooks/useNotificationEvents/index.tsx
NotificationProvider serves two related roles:
- Toast queue. Accepts NotificationContent payloads and renders them one at a time using a FIFO queue with deduplication.
- Event bus. Exposes sendEvent, subscribe, and unsubscribe so that any subtree component can publish or observe typed GlobalEvent objects without coupling to a DOM CustomEvent bus.
| Feature | Description |
|---|---|
| Deduplication | Notifications with identical signatures are silently dropped |
| Slide animation | CSS classes slide-in / slide-out are managed by the provider |
| Scoped vs. toast | displayMode: inline events are routed to subscribers only; toast events enter the queue |
| Event bridge | eventBridge.ts allows code outside the React tree to dispatch events before the provider mounts |
| Cleanup | The bridge dispatcher is unregistered on unmount via a useEffect return |
NotificationContext exposes the following shape:
type NotificationContextData = {
clearEvents: (eventTarget?: string) => void;
display: (content: NotificationContent) => void;
sendEvent: (event: GlobalEvent) => void;
subscribe: (eventName: EventType, listener: EventListener) => () => void;
unsubscribe: (eventName: EventType, listener: EventListener) => void;
};| Method | When to use |
|---|---|
| display | Directly enqueue a toast with full NotificationContent control |
| sendEvent | Publish a typed GlobalEvent; the provider decides how to display it |
| subscribe | Listen for events of a given severity in the current render tree |
| unsubscribe | Manually remove a listener (automatically called by subscribe's return value) |
| clearEvents | Broadcast a clear signal for a named eventTarget (inline banners) |
The preferred consumer-facing API is the useNotificationEvents hook, which wraps the context and adds the convenience helpers sendToastEvent and sendInlineEvent.
eventBridge.ts decouples the notification pipeline from the React render cycle. It holds a module-level reference to the active dispatcher and a pending queue:
let dispatcher: EventDispatcher | null = null;
let pendingEvents: GlobalEvent[] = [];When NotificationProvider mounts it calls registerNotificationEventDispatcher(sendEvent), which:
- Sets dispatcher to the provider's sendEvent function.
- Flushes any events that arrived before the provider was ready.
- Returns a cleanup function that nulls dispatcher when called.
Code outside the React tree (service workers, interceptors, module-level error handlers) calls dispatchNotificationEvent(event) without knowing whether the provider has mounted yet.
dispatchNotificationEvent(event)
|
v
dispatcher set?
+-----+------+
| yes | no |
v v
sendEvent push to pendingEvents[]
|
v
provider mounts -> flush
NotificationProvider registers and unregisters the bridge inside a single useEffect that depends on sendEvent:
useEffect(() => {
return registerNotificationEventDispatcher(sendEvent);
}, [sendEvent]);Because sendEvent is stabilized with useCallback, this effect runs only once per provider mount. The cleanup function returned by registerNotificationEventDispatcher ensures that dispatcher is set to null only if it still points to the current sendEvent instance, preventing stale cleanup from corrupting bridge state during fast remounts.
Use useNotificationEvents in any component below NotificationProvider:
import useNotificationEvents from '@/hooks/useNotificationEvents';
const MyComponent = () => {
const { sendToastEvent } = useNotificationEvents();
const handleSave = async () => {
await save();
sendToastEvent({
eventType: 'success',
title: 'Saved',
description: 'Your changes have been saved.',
});
};
};// Do not construct DOM CustomEvents.
window.dispatchEvent(new CustomEvent('notification', { detail: { title: 'Saved' } }));Some older components may still use the direct display API from notification context:
import { useNotification } from 'src/context/notification/useNotification';
const { display } = useNotification();
display({
title: 'Success!',
subtitle: 'Your action was successful.',
caption: 'This message will disappear shortly.',
kind: 'success',
timeout: 5000,
onClose: () => {},
onCloseButtonClick: () => {},
});Prefer useNotificationEvents for new code.
Use dispatchNotificationEvent from services, interceptors, or module-level handlers that run before the provider mounts:
import { dispatchNotificationEvent } from '@/context/notification/eventBridge';
dispatchNotificationEvent({
eventType: 'error',
title: 'Request Failed',
description: 'The server returned a 500 error.',
timeout: 8000,
});subscribe returns an unsubscribe function and is safe to use with useEffect:
import { useEffect } from 'react';
import useNotificationEvents from '@/hooks/useNotificationEvents';
const MyBanner = () => {
const { subscribe } = useNotificationEvents();
useEffect(() => {
return subscribe('error', (event) => {
if (event.eventTarget === 'my-section') {
// handle scoped error banner
}
});
}, [subscribe]);
};Inline events are routed only to subscribers and are not queued as toasts:
const { sendInlineEvent } = useNotificationEvents();
sendInlineEvent({
eventType: 'warning',
title: 'Validation Error',
description: 'Please correct the highlighted fields.',
eventTarget: 'waste-search-form',
});NotificationContent commonly includes:
- title: string
- subtitle?: string
- caption?: string
- kind: error | info | info-square | success | warning | warning-alt | undefined
- timeout: number (0 means remain visible until closed)
- onClose?: () => void
- onCloseButtonClick?: () => void
- children?: ReactNode
The provider drives two CSS classes, slide-in and slide-out, on the ToastNotification wrapper.
| Class | When set |
|---|---|
| slide-in | When display() is called; reset at the start of each new notification |
| slide-out | When auto-timeout fires or the user clicks close |
The provider uses two coordinated useEffect hooks:
- Slide-out trigger fires at timeout - 300 ms (minimum 0) to begin the exit animation before Carbon removes the element.
- Queue advance removes the queue head 300 ms after slide-out is set.
Both timers are cleared on cleanup to prevent state updates after unmount.
- Deduplication. Before enqueuing, the provider computes a signature from dedupeKey or [kind, scope, title, subtitle, caption].join('::').
- FIFO order. Notifications display in arrival order.
- Inline events skip the queue. displayMode: inline or non-empty eventTarget routes to subscribers only.
registerNotificationEventDispatcher returns a closure that compares the registered dispatcher before nulling it. This protects against stale cleanup during rapid remounts (for example in Strict Mode).
Always keep NotificationProvider near the app root so it is not unmounted during normal navigation.
- clearEvents is implemented as a special info event with title clear-events.
- EventType is limited to error, info, success, and warning.
- Prefer useNotificationEvents over direct NotificationContext usage.
- See Frontend Structure Guidelines for context usage boundaries and hook patterns.
This wiki documents NR Waste Plus for users and developers. Verify security and deployment guidance against the current application configuration before applying it elsewhere.
- Report an issue or ask a question in the GitHub repository.
- Follow the Contributing Guidelines to propose an update.
- Read the Code of Conduct.
Collection indexes support human browsing and machine retrieval. Keep both current when adding or moving wiki pages.
NR Waste Plus Wiki | GitHub Repository