4.1 KiB
4.1 KiB
useToastSonnerBridge
(no submodules)
Purpose
Bridge hook that exposes the legacy chassis useToast API on top of
sonner.toast. The original chassis useToast (Wave 2) ran a small
useSyncExternalStore-backed queue that a separate root-level
renderer painted; Wave 3g adopts sonner as the canonical toast
surface (per shadcn skill rule) so the chassis-side queue is no
longer the canonical path. This bridge lets consumers migrate one
import at a time — call sites that already imported the legacy
useToast hook keep working, and new call sites that want the
sonner-native surface adopt this bridge instead.
The two routes funnel through the same
react_ui.toast.shown_total{toast.kind=...} counter so observability
dashboards never double-count.
Features
bridge.toast({ titleKey, descriptionKey?, kind? })— branded translation keys, resolved against the active<TranslationProvider>, dispatched to sonner.bridge.dismiss(id?)— dismiss a specific toast id, or every active toast when called without arguments.- Kind-aware dispatch:
success,error,info,warningroute to the matchingtoast.<kind>method; everything else is dispatched via the defaulttoast()callable. - Zero internal state — the bridge is a stateless adapter; the authoritative queue lives inside sonner.
Usage: Basic Example
import {
Sonner,
useToastSonnerBridge,
} from "@suaveplan/react-ui";
function SaveButton(): JSX.Element {
const { toast } = useToastSonnerBridge();
return (
<>
<button
type="button"
onClick={() =>
toast({
titleKey: "react-ui.loading",
kind: "success",
})
}
>
Save
</button>
<Sonner />
</>
);
}
Usage: Advanced Example
import {
Sonner,
useToastSonnerBridge,
} from "@suaveplan/react-ui";
import { useEffect } from "react";
function NetworkBoundary(): JSX.Element {
const { toast, dismiss } = useToastSonnerBridge();
useEffect(() => {
const id = toast({
titleKey: "react-ui.loading",
descriptionKey: "react-ui.empty.noResults",
kind: "warning",
});
const timer = setTimeout(() => dismiss(id), 4000);
return () => clearTimeout(timer);
}, [toast, dismiss]);
return <Sonner position="top-right" />;
}
API Reference
useToastSonnerBridge(): UseToastSonnerBridgeReturn
Returns:
toast(input: BridgeToastInput): string | number— emits a toast via sonner. The returned id is the same id sonner assigns; pass it todismissto retire the specific toast.dismiss(id?: string | number): void— dismisses one toast by id, or all toasts whenidisundefined.
BridgeToastInput
titleKey: TranslationKey— REQUIRED branded key resolved against the active i18n catalog.descriptionKey?: TranslationKey— optional secondary line, also resolved against i18n.kind?: ToastKind—'default' | 'success' | 'error' | 'info' | 'warning'. Defaults to'default'.
Implementation Notes
- The bridge MUST be called inside a
<TranslationProvider>because it readsuseTranslation()to resolve the branded keys; outside a provider it throwsREACT_I18N.NO_PROVIDER. - The bridge does NOT instrument OTel directly — the underlying
toastre-export (which records the counter) is the single point of attribution so we never double-count when the bridge fans out to a kind-specific method. - The bridge does NOT read sonner's
useSonner()hook because the upstream API does not expose a way to subscribe to a SPECIFIC toast id, and the chassis bridge has no use for the global toast list at call time —toast.dismiss(id)is sufficient for the UX surface. - Memoised
useCallbackwrappers keep the returned object referentially stable across renders so consumers using the bridge inside auseEffectdependency array do not re-fire on every parent render.