Conversation
Introduce a providerless, store-backed toast system: add toast store, selectors, and top-level Toaster component; refactor Toast -> ToastItem and ToastContainer to render by id and subscribe per-message (improves rendering isolation). Add swipe-to-dismiss, animation hook (useToastAnimation), reduced-motion hook, and other hooks/helpers. Remove legacy ToastProvider and related tests; update integration tests to exercise the new API. Update dev and example dependencies for tooling and RN preset versions, and add FUNDING.yml.
Bump to v4 and migrate from a Context provider to a providerless API: add Toaster component, a singleton `toast` API, and `useToast()` that no longer causes sibling re-renders. Update README and example to document the new usage (Toaster + toast.show) and add an 8x stress-test example. Key code changes: - package.json: version → 4.0.0 and added subpath exports (/accessibility, /haptics, /sizing). - src/index.tsx: export only the core runtime symbols (Toaster, useToast, toast, configureToast) to improve tree-shaking. - src/types.ts: introduce HapticFeedback type, unify hapticFeedback usage, remove deprecated provider types. - src/store/toastStore.ts: fix updateMessage to guard against missing current message and merge patch correctly. - src/components/Toast.tsx: map extended haptic vocabulary to existing haptic patterns before triggering. - src/utils/accessibility.ts: add getReducedMotionAnimationConfig and validateWcag22 helpers for WCAG 2.2 compliance and reduced-motion handling. - Tests: update integration/unit tests to use async act flows and remove fake timers where appropriate to reflect microtask-batched dispatch. - example/src/App.tsx: switch from ToastProvider to Toaster + toast API, add stressTest button and example config (swipeToDismiss, maxVisible, overflow). - tsconfig.build.json: exclude test files from build artifacts. Why: reduce render churn by removing a Context boundary and using per-toast external-store subscriptions, improve accessibility to WCAG 2.2, provide a smaller main bundle via subpath exports, and introduce safer haptic/type handling and test harness adjustments for the new microtask-batched behavior.
Remove subpath exports and streamline public surface (README + package.json + index) so only core runtime symbols are exposed. Simplify accessibility utilities (smaller API, trimmed docs and constants) and update types comments for clarity. Replace complex haptic mapping in Toast with direct normalize/trigger call and add platform/TS lint ignores. Update and tighten tests (remove advanced sizing/haptics suites, adjust haptics tests, type some test inputs, and simplify accessibility tests) and small styling test/type fixes. Overall cleanup reduces bundle surface and aligns tests with the refactored utilities.
Update tsconfig.json to enable additional compiler checks: - erasableSyntaxOnly: true — restricts code to syntax that can be erased safely during emit - libReplacement: false — disable automatic library replacement - noUncheckedSideEffectImports: true — error on imports with potential side effects that aren't explicitly checked These options tighten type- and module-related checks to catch unsafe patterns and unexpected import side effects earlier.
Set the TypeScript compiler option "isolatedModules" to true in tsconfig.json so each file can be transpiled independently. This is required for single-file transpilers (Babel/ESBuild/ts-jest/ts-node) and helps catch patterns that break isolated compilation and supports faster incremental builds.
Introduce an O(1) byId index to the toast store and use it throughout (getMessage, updateMessage, remove, clear) to avoid repeated linear scans. Batch and optimize operations: rebuild index on setMessages, batch promotions from the queue, reduce allocations, and use for-of loops for listeners/timers. Also expand and improve public docs and API surface: richer README and JSDoc for Toaster, useToast, toast, configureToast alias, index exports, and types; clarify hook/imperative usage and examples. Overall this improves performance, reduces re-renders/allocations, and clarifies public API surface.
Delete an unused require('react') from the ToastContainer mock in useToast.test.tsx to remove a stray variable. Add "types": ["jest"] to tsconfig.json so TypeScript picks up Jest typings (fixes test type/import issues).
There was a problem hiding this comment.
Pull request overview
This PR upgrades react-native-rooster to v4 with a providerless architecture centered around a module-level external store, introducing an imperative toast facade and a new <Toaster /> mount point while modernizing container/toast rendering for fewer re-renders.
Changes:
- Replaces the Context/Provider architecture with an external store +
useSyncExternalStore, adding<Toaster />andtoast/configureToast. - Refactors toast layout/rendering to be id-driven (
ToastContainerreceives ids; each toast subscribes to its own message slice). - Updates tooling/docs/tests and removes legacy utilities/tests/provider code.
Reviewed changes
Copilot reviewed 42 out of 44 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tsconfig.json | Tightens TypeScript compiler settings and adds Jest types. |
| tsconfig.build.json | Excludes tests from build compilation. |
| src/utils/styling.ts | Simplifies style utilities and constants; keeps backwards-compat keys. |
| src/utils/sizing.ts | Removes sizing utilities module (v3-era). |
| src/utils/positioning.ts | Simplifies positioning utilities and introduces constants. |
| src/utils/haptics.ts | Reworks haptics to normalize a broader vocabulary and simplify patterns. |
| src/utils/accessibility.ts | Simplifies accessibility helpers and removes several exported utilities. |
| src/utils/tests/styling.test.ts | Updates styling tests for new exports/typing. |
| src/utils/tests/sizing.test.ts | Removes sizing tests (module removed). |
| src/utils/tests/positioning.advanced.test.ts | Tightens types in positioning tests. |
| src/utils/tests/mergeToastConfig.test.ts | Improves typing in merge tests and updates haptics typing. |
| src/utils/tests/haptics.test.ts | Updates haptics tests for new normalize/trigger APIs. |
| src/utils/tests/haptics.advanced.test.ts | Removes redundant advanced haptics tests. |
| src/utils/tests/accessibility.test.ts | Updates tests to match the simplified accessibility surface. |
| src/types.ts | Adds HapticFeedback, introduces ToastApi/ToasterProps, and expands config options. |
| src/toast.ts | Adds imperative toast facade and configureToast alias. |
| src/store/useToastSelector.ts | Adds useSyncExternalStore selectors for ids/config/messages. |
| src/store/toastStore.ts | Adds module-level external store with ids/config/message subscriptions and overflow logic. |
| src/store/tests/toastStore.test.ts | Adds unit tests for store behavior (add/remove/timers/overflow/subscriptions). |
| src/providers/tests/ToastProvider.test.tsx | Removes provider-based test suite. |
| src/providers/ToastProvider.tsx | Removes the v3 provider implementation. |
| src/index.tsx | Updates public entrypoint exports to Toaster/useToast/toast surface and new types. |
| src/hooks/useToastAnimation.ts | Adds per-toast animation lifecycle hook (enter/exit + pause/resume + reduced motion). |
| src/hooks/useToast.ts | Changes hook to return the singleton toast API (no provider required). |
| src/hooks/useReducedMotion.ts | Adds reduced-motion subscription hook. |
| src/hooks/tests/useToast.test.tsx | Updates hook tests for providerless behavior + Toaster integration. |
| src/constants/defaultConfig.ts | Adds new default config values (maxVisible/overflow/stagger/etc). |
| src/components/tests/Toaster.test.tsx | Adds tests for new <Toaster /> behavior. |
| src/components/tests/ToastContainer.test.tsx | Updates container tests for id-driven rendering and ordering. |
| src/components/tests/Toast.test.tsx | Updates toast tests to store-subscribed id-based ToastItem. |
| src/components/Toaster.tsx | Adds <Toaster /> component mounting store-backed container (lazy render + config merge + multi-mount warning). |
| src/components/ToastContainer.tsx | Refactors container to accept ids and render ToastItem per id. |
| src/components/Toast.tsx | Refactors toast component into id-subscribed ToastItem with swipe-to-dismiss + new animation hook. |
| src/tests/integration.test.tsx | Updates integration tests for providerless v4 API and imperative facade. |
| src/tests/index.test.tsx | Removes provider-era integration tests. |
| src/tests/fontSizeConfig.test.tsx | Removes provider-era font-size config tests. |
| src/contexts/ToastContext.ts | Removes legacy context (no longer used). |
| package.json | Bumps to v4 and updates dev tooling/dependency setup (incl. Jest preset switch). |
| example/src/App.tsx | Updates example app to <Toaster /> + toast stress test and new config props. |
| example/package.json | Updates example dependencies for latest Expo/RN versions. |
| biome.json | Updates Biome schema version reference. |
| README.md | Rewrites docs for v4 providerless architecture and new API surface. |
| .github/FUNDING.yml | Adds funding configuration file. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+66
to
+67
| "@react-native/babel-preset": "0.x", | ||
| "@react-native/jest-preset": "0.x", |
| if (duration <= 0 || !Number.isFinite(duration)) return; | ||
| const handle = setTimeout(() => { | ||
| timers.delete(id); | ||
| remove(id); |
Comment on lines
+235
to
+250
| if (state.messages.length >= max) { | ||
| if ((state.config.overflow ?? 'evict') === 'queue') { | ||
| state.queue = state.queue.concat(message); | ||
| return message.id; | ||
| } | ||
| // 'evict' — drop the oldest with a fast exit | ||
| const evicted = state.messages[0]; | ||
| if (evicted) { | ||
| // Re-arm timer to fire (almost) immediately so the UI plays exit anim | ||
| clearTimer(evicted.id); | ||
| startTimer(evicted.id, FAST_EVICT_DURATION); | ||
| } | ||
| } | ||
|
|
||
| setMessages(state.messages.concat(message)); | ||
| startTimer(message.id, effectiveDurationFor(message)); |
Comment on lines
+136
to
+164
| (duration: number) => { | ||
| clearDismissTimer(); | ||
| if (duration <= 0) return; | ||
| remainingRef.current = duration; | ||
| startedAtRef.current = Date.now(); | ||
| pausedRef.current = false; | ||
| dismissTimerRef.current = setTimeout(() => { | ||
| dismissTimerRef.current = null; | ||
| dismiss(); | ||
| }, duration); | ||
| }, | ||
| [clearDismissTimer, dismiss], | ||
| ); | ||
|
|
||
| const pauseAutoDismiss = useCallback(() => { | ||
| if (pausedRef.current || !dismissTimerRef.current) return; | ||
| pausedRef.current = true; | ||
| const elapsed = Date.now() - startedAtRef.current; | ||
| remainingRef.current = Math.max(0, remainingRef.current - elapsed); | ||
| clearDismissTimer(); | ||
| }, [clearDismissTimer]); | ||
|
|
||
| const resumeAutoDismiss = useCallback(() => { | ||
| if (!pausedRef.current) return; | ||
| pausedRef.current = false; | ||
| if (remainingRef.current > 0) { | ||
| startAutoDismiss(remainingRef.current); | ||
| } | ||
| }, [startAutoDismiss]); |
Comment on lines
157
to
161
| const type = message.type ?? 'info'; | ||
| const isInteractive = !!message.onPress; | ||
|
|
||
| // Use custom label if provided, otherwise generate from title/message | ||
| const label = | ||
| message.accessibilityLabel ?? generateAccessibilityLabel(message); | ||
|
|
||
| // Use custom hint if provided, otherwise generate from type and interaction | ||
| const hint = |
Introduce dismissHandlers to allow animation-aware dismiss callbacks when auto-dismiss timers expire (so the animation layer can play exit animations before removal). Add _registerDismissHandler (exported as registerDismissHandler) which registers/unregisters handlers per toast id. Update timer logic to invoke a handler if present, falling back to remove(id) for test ergonomics. Improve eviction logic to drop multiple oldest toasts in one batched update and schedule fast-evict timers so mounted items can animate out. Ensure dismissHandlers are cleared on remove/clear/__resetForTests. Also bump @react-native/babel-preset and @react-native/jest-preset to ~0.83.6 in package.json.
Centralize auto-dismiss timing in toastStore and remove timer logic from the animation hook. useToastAnimation now imports toastStore, forwards pauseAutoDismiss/resumeAutoDismiss to toastStore, registers a dismiss handler so the store can trigger the exit animation, and drops internal timeout/state bookkeeping. Also simplify unmount cleanup to only stop animations. In Toast, always mark items as interactive (tap-dismissible) so accessibility hints consistently advertise tap-to-dismiss.
Removed the leading underscore from the dismiss handler registration function in src/store/toastStore.ts to make the name consistent and non-private. No behavior changes; the function signature and return remain the same.
Co-authored-by: Copilot <copilot@github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This pull request introduces a major version update (v4.0.0) for the
react-native-roostertoast notification library, focusing on a new providerless architecture, improved performance, and a simplified, more flexible API. The example app and core library have been refactored to use the new imperative API, and the toast container logic has been modernized for better separation of concerns and efficiency. Additionally, dependencies and development tooling have been updated, and obsolete test suites and provider-based patterns have been removed.Core Library Refactor and API Changes:
ToastProvider) to a providerless, imperative API usingToasterandtoast, simplifying integration and usage. (example/src/App.tsx,package.json,src/components/ToastContainer.tsx, [1] [2] [3]ToastContainerto accept only a list of toast IDs, with each toast subscribing to its own message slice, improving performance and reducing unnecessary re-renders. (src/components/ToastContainer.tsx, [1] [2] [3]src/__tests__/index.test.tsx,src/__tests__/fontSizeConfig.test.tsx, [1] [2]Example App Updates:
Toaster,toast) and demonstrate new features such as stress testing, global configuration, and swipe-to-dismiss. (example/src/App.tsx, [1] [2] [3] [4]example/package.json, example/package.jsonL12-R23)Dependency and Tooling Updates:
package.json,biome.json, [1] [2] [3]@react-native/jest-presetfor improved test compatibility. (package.json, package.jsonL101-R94)Project Maintenance:
.github/FUNDING.ymlfile to support open source funding via multiple platforms. (.github/FUNDING.yml, .github/FUNDING.ymlR1-R15)References:
[1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15]