Skip to content

context notification

Paulo Gomes da Cruz Junior edited this page Jun 16, 2026 · 4 revisions

NotificationProvider

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:


Component Overview

NotificationProvider serves two related roles:

  1. Toast queue. Accepts NotificationContent payloads and renders them one at a time using a FIFO queue with deduplication.
  2. 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.

Key features

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

Architecture

Context API

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.

Event bridge pattern

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:

  1. Sets dispatcher to the provider's sendEvent function.
  2. Flushes any events that arrived before the provider was ready.
  3. 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

Lifecycle and cleanup mechanics

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.


Usage Examples

Display a notification through context

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' } }));

Legacy display API (when needed)

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.

Send a global event from outside React

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 to events

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]);
};

Send an inline (scoped) event

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 Fields

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

Animation Behavior

The provider drives two CSS classes, slide-in and slide-out, on the ToastNotification wrapper.

State machine

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

Timeout handling

The provider uses two coordinated useEffect hooks:

  1. Slide-out trigger fires at timeout - 300 ms (minimum 0) to begin the exit animation before Carbon removes the element.
  2. 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.

Queue management

  • 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.

Event Dispatcher Cleanup

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.


Notes

  • 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.

See Also

Waste Plus Wiki

Home

Architecture

Codes

Decisions

Knowledge Base

Development

Changes

Clone this wiki locally