Quick start

Create one controller, mount a Provider and a Toaster, and show the first notification.

On this page

By the end of this page your application has one controller, a Toaster on screen and a first notification in the stack. It assumes the package and its CSS are installed as described in Installation.

Create the controller

The controller owns every notification, its time and its announcements. Create it once per browser session, where your application starts, not inside a component that renders again. Creating it allocates no timers, listeners or DOM, so doing it at module scope of the client entry is cheap.

main.tsx
import '@cloudlet/toast/styles.css';
import { createRoot } from 'react-dom/client';
import { CloudletProvider, createCloudlet, Toaster } from '@cloudlet/toast';

// Once, when the browser application starts.
const cloudlet = createCloudlet();

createRoot(document.getElementById('root')!).render(
  <CloudletProvider controller={cloudlet}>
    <Toaster />
  </CloudletProvider>,
);

CloudletProvider only hands the controller to the tree below it; Toaster finds it in context and renders the stack and the Island. Mount one Toaster per controller. The Toaster renders nothing on the server and portals into the document after mount, so it can sit anywhere under the Provider.

In a Next.js App Router project the bootstrap is a client component instead of a module: it creates the controller in an effect and disposes it in the cleanup, as in the shadcn tab of Installation. On the server, create a controller per request and never share one between users.

Show a notification

Any component under the Provider gets the controller with useCloudlet() and calls show. The call returns an admission result right away: the record was created, merged into an identical one, or rejected, with the reason.

Snippets assume const cloudlet = useCloudlet(); — Run calls them on this page’s own Toaster.

cloudlet.show({ title: 'Synced to the cloud', kind: 'success' });

A success notification without an action stays for six seconds of time on screen. Hovering it, focusing it or switching to another tab pauses that time; it never disappears while someone is reading it.

The whole example

The package README puts the pieces into one file: a component that calls show from a click, and a Notifications component that receives the controller from the bootstrap and mounts the Provider and the Toaster around the application.

notifications.tsx
import '@cloudlet/toast/styles.css';
import { CloudletProvider, createCloudlet, Toaster, useCloudlet } from '@cloudlet/toast';

function SaveNotice() {
  const cloudlet = useCloudlet();
  return <button onClick={() => cloudlet.show({ title: 'Saved', kind: 'success' })}>Notify</button>;
}

// The browser application bootstrap creates and owns this controller.
export function Notifications({ controller }: { controller: ReturnType<typeof createCloudlet> }) {
  return <CloudletProvider controller={controller}>
    <SaveNotice />
    <Toaster />
  </CloudletProvider>;
}

When to dispose

dispose() is final: it closes every notification, releases the controller's timers and subscriptions, and every later call returns disposed. Business operations you tracked keep running to their own result. Call it only when the session the controller belongs to is over: a test that finished, a micro-frontend removed for good, a server request that has been answered. A single-page application usually never calls it; the controller lives as long as the page.

Unmounting the Toaster is not the end of the controller. It pauses visible time and keeps every record and every tracked operation; mounting a Toaster again resumes the same queue.

React Strict Mode

In development Strict Mode mounts, unmounts and mounts every component again, replaying effects. Two rules keep Cloudlet correct under it:

  • Do not dispose a controller you received from outside in an effect cleanup. The replayed cleanup would dispose the controller the next mount still uses.
  • An effect that creates its own controller disposes exactly that instance in its cleanup and creates a fresh one on the next setup. The client owner in Installation does this.

What not to do

  • Calling show during render. Render runs again on every state change, and twice in Strict Mode. Call show from an event: a click, a finished request, a message from the server.
  • Creating the controller in a component body. Every render would create a new controller with an empty stack. Create it in the bootstrap or in an effect of a client owner.
  • Sharing a server controller between users. On the server a controller belongs to one request.
  • Keeping important data only in a notification. A notification is a notice; anything the user must not lose also lives in your interface.

Next steps