Skip to content

fix: survive fast refresh without disposing native objects#213

Open
mfazekas wants to merge 5 commits intomainfrom
fix/fast-refresh-dispose-main
Open

fix: survive fast refresh without disposing native objects#213
mfazekas wants to merge 5 commits intomainfrom
fix/fast-refresh-dispose-main

Conversation

@mfazekas
Copy link
Copy Markdown
Collaborator

Summary

Cherry-pick of #212 onto main. Fixes #205.

Fast refresh (HMR) re-runs all effect cleanups then all effects. Hooks that disposed native HybridObjects in effect cleanup would destroy the backing native state, but useMemo caches still returned the same (now dead) JS references — causing NativeState is null crashes.

Introduces useDisposableMemo — a useMemo replacement for disposable native resources:

  • Value created synchronously during render (available on first render)
  • Old value disposed in render phase when deps change
  • Prod: synchronous dispose on unmount (zero overhead)
  • Dev: deferred dispose via setTimeout(0), cancelled if fast refresh / Strict Mode re-mounts the component

Migrates useRiveProperty, useRiveList, and useViewModelInstance.

Test plan

  • 9 unit tests for useDisposableMemo
  • All existing tests pass
  • Tested on iOS with Data Binding exerciser — fast refresh survives

Introduce useDisposableMemo — a useMemo replacement for native-backed
objects that need deterministic cleanup. Disposal happens in the render
phase on deps change, and on unmount via deferred setTimeout(0) in dev
(cancelled if fast refresh re-mounts) or synchronously in production.

Migrates useRiveProperty, useRiveList, and useViewModelInstance to use
it, fixing the "NativeState is null" crash on HMR.
When deps change, useDisposableMemo disposes the old property during
render, but the useEffect cleanup for that property runs later. Wrap
removeListener/removeListeners calls in try-catch since native dispose()
already handles listener cleanup. Also protect render-phase cleanup in
useDisposableMemo against throws.
Covers first render, deps change, unmount, rapid cycling, undefined
factory, throwing cleanup, sequential changes, and Object.is semantics.
Also wraps deferred setTimeout cleanup in try-catch matching the
render-phase protection.
@mfazekas mfazekas requested a review from HayesGordon April 14, 2026 06:54
@mfazekas mfazekas marked this pull request as ready for review April 14, 2026 06:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fast refresh breaks Rive

1 participant