Performance and Async Behavior

Boreal UI keeps timers, polling, and transient state local to each mounted instance. Use stable data identities and the documented async contracts to keep applications responsive and predictable.

Isolated timers

Chips, toasts, modals, notifications, and uploads clean up work per mounted instance without affecting sibling components.

Completion-based polling

Notification and Select polling waits for the current request to settle before scheduling the next one, preventing overlap.

Stable table identity

Stable row keys and referentially stable data preserve selection, expansion, and efficient rendering across table updates.

Timed Feedback

Timers belong to the nearest mounted component or provider and are cleared when their owner unmounts.

  • Each mounted Chip owns its auto-close and exit timers.
  • Repeated Chip close requests are coalesced during the 300 ms exit window.
  • ToastProvider replaces an existing toast ID and restarts that toast's expiry.
  • Modal coalesces overlay, Escape, and close-button requests during its exit.
  • Pending timers and animation-frame work are canceled on unmount.
chip-timers.tsxtsx
<Chip
  id="saved-chip"
  message="Saved"
  visible={showSaved}
  duration={1500}
  onClose={() => setShowSaved(false)}
/>
toast-replacement.tsxtsx
const { addToast } = useToast();

addToast({
  id: "profile-save",
  message: "Saving profile...",
  duration: 0,
});

addToast({
  id: "profile-save",
  message: "Profile saved",
  duration: 2500,
});

Polling

Requests never overlap: the next timeout begins only after the current async request settles.

notification-center.tsxtsx
<NotificationCenter
  notifications={notifications}
  onRemove={removeNotification}
  fetchNotifications={loadNotifications}
  pollInterval={10_000}
/>
async-select.tsxtsx
<Select
  aria-label="Project"
  value={projectId}
  onChange={setProjectId}
  options={[]}
  asyncOptions={loadProjects}
  pollInterval={30_000}
/>

Set pollInterval={0} to perform the initial load without later polling. Notification expiry uses the latestonRemove callback without restarting existing deadlines.

DataTable Rendering

Give mutable, paginated, sorted, or virtualized records an application-level identity.

stable-table.tsxtsx
const columns = useMemo(() => createColumns(), []);
const selectedKeys = useMemo(() => [...selection], [selection]);

<DataTable
  columns={columns}
  data={rows}
  rowKey={(row) => row.id}
  selectedRowKeys={selectedKeys}
  pagination
  selectableRows
/>
  • Provide rowKey whenever order can change or data can be replaced.
  • Keep large columns, data, and controlled key arrays referentially stable.
  • Use standalone imports such as @boreal-ui/next/DataTable for the clearest bundle boundary.

ThemeProvider Rendering

Server-generated theme attributes avoid a first-paint flash and remove the need for a duplicate initialization script.

Stable custom schemes

Equivalent custom scheme arrays reuse the existing snapshot. Keep large collections stable to avoid repeated serialization work.

No duplicate theme script

Next's provider defaults enableThemeScript to false. Keep it disabled when the server already applies theme attributes from the Boreal cookie.

Next.js Scroll Restoration

Boreal globals leave root scrolling at auto. If an app deliberately enables smooth scrolling on html, adddata-scroll-behavior="smooth" so Next.js can temporarily disable it during route restoration.