Skip to content

React 19 support, migrate to @react-spring/web and @use-gesture/react - #2

Merged
Guiw5 merged 3 commits into
mainfrom
chore/react19-perf
Sep 8, 2026
Merged

Guiw5 merged 3 commits into
mainfrom
chore/react19-perf

Conversation

@Guiw5

@Guiw5 Guiw5 commented Sep 8, 2026

Copy link
Copy Markdown
Owner

Summary

Migrates the sheet off react-spring v8 / react-use-gesture (both unmaintained, and v8 breaks under React 18/19 StrictMode: it destroys its controllers on the dev double-mount and never recreates them) onto @react-spring/web 10 and @use-gesture/react 10, and adds React 19 to the peer deps. Also fixes several regressions from the XState v5 migration.

Fixes

  • skipInitialTransition was ignored: the machine never received initialState, now passed via input.
  • data-rsbs-state was only set for atomic states (current.value === 'opening' is false for nested values), so the opening/closing CSS never applied. Uses matches() now.
  • console.log/console.count in every render, in useReady, in the focus trap, plus debug actors with sleep(1000) shipped in the bundle.
  • onSpringStart / onSpringEnd promises were not awaited, so returning a promise no longer delayed the transition.
  • Operator precedence bug in useMaxHeight initial state.
  • SpringConfig required every key; it is now Partial.
  • NODE_ENV=production in the build script did not run on Windows (cross-env).
  • postcss-svgo was a runtime dependency; moved to devDependencies.

Performance

  • Machine transitions no longer re-render React: only the data-rsbs-state attribute depended on them, so it is written to the DOM from an actor subscription (useActorRef). Opening previously triggered ~8 renders mid-animation.
  • Machine implementations and spring interpolations are created once instead of on every render.
  • Dropped @juggle/resize-observer: it polyfills even when the native API exists and recomputes on every DOM mutation. Native ResizeObserver with a borderBoxSize fallback.
  • Modern browserslist (native ResizeObserver baseline) so less transpilation output.

Compatibility

  • Peer deps: react and react-dom ^16.14 || ^17 || ^18 || ^19.
  • Types compile against both @types/react 18 and 19 (no global JSX, no useRef() without argument, no MutableRefObject).
  • aria-modal follows blocking; closed-but-mounted sheets (keepMounted) get visibility: hidden so they leave the tab order and a11y tree.
  • Docs site runs with reactStrictMode: true so the v8 StrictMode bug cannot regress silently.

Behaviour note

The published version animates with a 115ms linear tween (duration: 115 overrides tension/friction/velocity). Kept as the default; springConfig={{ duration: undefined }} restores spring physics. Documented on SpringConfig.

Numbers

before after
Bundle incl. deps (gzip) 39.4 kB 49.9 kB (react-spring v10 is ~2x v8)
Frames > 33ms open/drag/close, 4x CPU throttle n/a 3 of 198, p95 16.9ms

Test plan

  • npm test (library build, declarations, Next docs build)
  • tsc with @types/react 18 and 19, eslint
  • Chrome (mobile emulation, StrictMode): open/close, Escape dismiss, header drag with snap, multi snap points, content resize, scroll lock and aria-hidden restore
  • Real iOS Safari and Android Chrome

…/react

Drop react-spring v8 (broken under React 18/19 StrictMode), react-use-gesture and the ResizeObserver polyfill. Wire the XState machine once via useActorRef so transitions no longer re-render, fix skipInitialTransition and the data-rsbs-state attribute, remove debug logging.
@Guiw5
Guiw5 merged commit 024589a into main Sep 8, 2026
3 checks passed
@Guiw5 Guiw5 mentioned this pull request Sep 8, 2026
3 of 4 tasks
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 3.9.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant