Skip to content

Conversation

@flaviendelangle
Copy link
Member

@flaviendelangle flaviendelangle commented Dec 8, 2025

This should not be part of the stable release it can be reviewed after 👍

  • Introduce a store in ToastProvider
  • Use a memoized selectors to compute the index, visible index and offset of each toast (instead of looping on the list of each item)
  • Move all the actions to a store class to reduce the usage of useStableCallback

@flaviendelangle flaviendelangle self-assigned this Dec 8, 2025
@flaviendelangle flaviendelangle added type: enhancement It’s an improvement, but we can’t make up our mind whether it's a bug fix or a new feature. component: toast Changes related to the toast component. labels Dec 8, 2025
@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 8, 2025

commit: 484388b

@mui-bot
Copy link

mui-bot commented Dec 8, 2025

Bundle size report

Bundle Parsed size Gzip size
@base-ui/react 🔺+2.82KB(+0.68%) 🔺+640B(+0.48%)

Details of bundle changes


Check out the code infra dashboard for more information about this PR.

@netlify
Copy link

netlify bot commented Dec 8, 2025

Deploy Preview for base-ui ready!

Name Link
🔨 Latest commit 484388b
🔍 Latest deploy log https://app.netlify.com/projects/base-ui/deploys/697b12395825ac00081a303d
😎 Deploy Preview https://deploy-preview-3464--base-ui.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged. label Dec 8, 2025
@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged. label Dec 8, 2025

export interface ToastManagerUpdateOptions<Data extends object>
extends Partial<ToastManagerAddOptions<Data>> {}
extends Partial<Omit<ToastObject<Data>, 'id'>> {}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows to use store.updateToast instead of store.set('toasts, ....) in Toast.Root.
If you want to keep some properties private (even though their is no runtime safe guard, it's only typing), I can create a store.updateToastPublic method that keeps the previous typing and expose it in Toast.useToastManager instead of store.updateToast

return (
<ToastViewportContext.Provider value={contextValue}>
{numToasts > 0 && prevFocusElement && <FocusGuard onFocus={handleFocusGuard} />}
<React.Fragment>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This context was never used.
But if we need viewportRef, we can now use useToastContext since it's stable accross render 👌

resumeTimers,
viewportRef,
windowFocusedRef,
setFocused,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me the logic still works here but I would like a double check 👀

@flaviendelangle flaviendelangle marked this pull request as ready for review December 9, 2025 09:24
@michaldudak
Copy link
Member

I'd rather not include this in v1. Introducing a Store to other components usually came with a couple of subtle bugs, so let's test it without rushing after the release.

@flaviendelangle
Copy link
Member Author

I'd rather not include this in v1.

Clearly, I'm not in favor of rushing anything before the stable, unless the gains are huge 👍

@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged. label Dec 9, 2025
@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged. label Dec 9, 2025
@flaviendelangle
Copy link
Member Author

@greptileai

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 12, 2026

Greptile Overview

Greptile Summary

This PR successfully refactors the toast system to use a centralized store pattern, significantly improving code organization and performance.

Key Changes:

  • Introduced ToastStore class that centralizes all toast state management and business logic
  • Implemented memoized selectors to efficiently compute toast indices, visible indices, and offsets (avoiding O(n²) loops for each toast)
  • Moved all actions (addToast, closeToast, updateToast, promiseToast) into store methods, reducing the need for useStableCallback wrappers
  • Proper cleanup implemented via disposeEffect method that clears all active timers on unmount
  • Simplified ToastProvider from ~380 lines to ~60 lines by delegating logic to the store
  • Updated all components to use useStore hook with selectors for reactive state access
  • Fixed setTimeout usage in ToastViewport to use useTimeout utility (adhering to repository guidelines)

Benefits:

  • Better separation of concerns (business logic in store, UI logic in components)
  • Improved performance through memoized selectors
  • More maintainable codebase with reduced complexity in provider
  • Consistent patterns with other stores in the codebase (e.g., PopoverStore)

Confidence Score: 5/5

  • This PR is safe to merge with no identified issues
  • The refactoring is well-executed with proper cleanup patterns, follows repository conventions, and all previous review concerns have been addressed (including the setTimeout issue). The store implementation mirrors established patterns from other stores in the codebase.
  • No files require special attention

Important Files Changed

Filename Overview
packages/react/src/toast/store.ts New store implementation with proper cleanup, memoized selectors, and timer management
packages/react/src/toast/provider/ToastProvider.tsx Successfully refactored to use store pattern, removing ~280 lines of logic moved to store
packages/react/src/toast/provider/ToastProviderContext.ts Simplified context type from verbose interface to store reference
packages/react/src/toast/root/ToastRoot.tsx Updated to use store with memoized selectors for index, visibleIndex, and offsetY
packages/react/src/toast/viewport/ToastViewport.tsx Refactored to use store pattern with proper useTimeout instead of window.setTimeout

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6 files reviewed, 6 comments

Edit Code Review Agent Settings | Greptile

@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged. label Jan 14, 2026
@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged. label Jan 28, 2026
@flaviendelangle
Copy link
Member Author

@greptileai

@flaviendelangle
Copy link
Member Author

@michaldudak this PR is ready for another round of review 🙏

@michaldudak
Copy link
Member

Review by Codex:

The new updateToast implementation only mutates the toast list and no longer adjusts timers when update() changes timeout or type. This breaks existing behaviors where updating a toast from timeout: 0 to a positive value should start auto-dismiss, or updating a loading toast to a non-loading type should schedule a timer; conversely, setting timeout: 0 won’t cancel an already running timer. This is a regression from the previous provider logic and will cause updated toasts to never dismiss or to dismiss unexpectedly based on stale timers.

@flaviendelangle
Copy link
Member Author

@michaldudak I think the did the review before this morning's fix.
Here is Claude Code answer (we really live in a wonderful world 😆 )

Analysis
The Original Problem
The commit 4da0b69 ("Move actions to the store") had a simple updateToast implementation that only mutated the toast list:

updateToast(id: string, updates: ToastManagerUpdateOptions) {
this.set('toasts', this.state.toasts.map((toast) =>
toast.id === id ? { ...toast, ...updates } : toast
));
}
This was a regression because the original ToastProvider code had timer adjustment logic in the update function.

The Fix (Already Applied)
The commit b39a218 ("Fix tests + review: Michal") from today added the timer management logic back to updateToast at store.ts:174-218. The fix:

Cancels timers when timeout: 0 is set or type changes to loading
Schedules new timers when:
Timeout changes from 0 to a positive value
Type changes from loading to another type
Timeout value is explicitly updated
Pauses timers appropriately when viewport is expanded/out of focus
The promiseToast method was also simplified to delegate timer handling to updateToast by passing timeout in the update options.

Verdict
No action required - the issue identified by Codex has been addressed in the latest commit. The current implementation in store.ts:174-218 properly handles all the timer scenarios:

timeout: 0 → positive value: starts auto-dismiss timer
type: 'loading' → 'success'/'error': schedules timer
timeout: X → timeout: 0: cancels running timer

@michaldudak
Copy link
Member

Yup, Codex agrees, it's fixed :)

export const ToastContext = React.createContext<ToastContext<any> | undefined>(undefined);
export const ToastContext = React.createContext<ToastContext | undefined>(undefined);

export function useToastContext() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we can call it useToastStore to differentiate it from useToastRootContext?

Copy link
Member

@michaldudak michaldudak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one nit. Besides that it looks good to me

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component: toast Changes related to the toast component. type: enhancement It’s an improvement, but we can’t make up our mind whether it's a bug fix or a new feature.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants