Skip to content

fix(animated): WeakMap fallback for non-extensible component caching#2534

Closed
AndreiCalazans wants to merge 125 commits into
pmndrs:mainfrom
AndreiCalazans:fix/non-extensible-component-cache
Closed

fix(animated): WeakMap fallback for non-extensible component caching#2534
AndreiCalazans wants to merge 125 commits into
pmndrs:mainfrom
AndreiCalazans:fix/non-extensible-component-cache

Conversation

@AndreiCalazans

Copy link
Copy Markdown

Fixes #2533

Problem

createHost caches animated wrappers via Component[cacheKey] = .... On Hermes, React Native host components (View, Text, Image) become non-extensible after their first JSX render — writing a new property throws TypeError: cannot add a new property in strict mode.

This crashes @react-spring/native's module initialization when loaded lazily (e.g. with Metro inlineRequires: true), because createHost runs at module scope iterating over those exact primitives.

Fix

Try the direct write first (fast path, no change for extensible components). On failure, fall back to a module-level WeakMap.

+const fallbackCache = new WeakMap<object, any>()

 const animated: WithAnimated = (Component: any) => {
   if (is.str(Component)) {
     Component = animated[Component] || (animated[Component] = withAnimated(Component, hostConfig))
   } else {
-    Component = Component[cacheKey] || (Component[cacheKey] = withAnimated(Component, hostConfig))
+    let cached = Component[cacheKey] ?? fallbackCache.get(Component)
+    if (!cached) {
+      cached = withAnimated(Component, hostConfig)
+      try {
+        Component[cacheKey] = cached
+      } catch {
+        // non-extensible component — store in WeakMap fallback
+      }
+      fallbackCache.set(Component, cached)
+    }
+    Component = cached
   }
 }

renovate Bot and others added 30 commits May 13, 2025 13:59
…ndrs#2360)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Neveux <d.neveux@warfog.gg>
Co-authored-by: Josh <joshua.ellis18@gmail.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Neveux <d.neveux@warfog.gg>
Co-authored-by: Dimitris Tsetsonis <jimtse12@gmail.com>
Co-authored-by: Josh <joshua.ellis18@gmail.com>
joshuaellis and others added 27 commits May 22, 2026 09:49
refractor v5 reorganised its exports map to `./*` -> `./lang/*.js`,
so the old `refractor/lang/tsx` resolves to `lang/lang/tsx.js`. Use
the new `refractor/tsx` and `refractor/jsx` paths.
Two unrelated regressions surface under @vanilla-extract/css 1.20.1
+ lightningcss minification:

- Wrap orphan `screen and (...)` selectors in an `'@media'` key on
  `_index.homeBlocks` and `TableCell.tableCellIsThirdItem`. Older
  vanilla-extract silently treated them as media queries; the new
  version emits them as native CSS nesting, which lightningcss
  rejects.

- Use `calloutWrapper.classNames.base` instead of `${calloutWrapper}`
  in the `Callout` globalStyle selector. @vanilla-extract/recipes
  0.5.7 dropped the implicit `toString()` on the runtime fn, so
  interpolation leaked the function source into the CSS file.
React 19's stricter `cloneElement` signature rejects the
`aria-hidden`/`focusable`/`className` props against
`ReactElement<unknown>`. Cast the only child to
`ReactElement<HTMLAttributes<HTMLElement>>` so the props validate.
On Hermes (React Native), host components like View, Text, and Image
become non-extensible after their first JSX render. The existing cache
mechanism writes directly to Component[cacheKey], which throws
'cannot add a new property' in strict mode for these objects.

Fix: attempt the direct write first (fast path for extensible components),
catch the TypeError, and fall back to a module-level WeakMap for
non-extensible components.

Reproduces with Metro inlineRequires:true + experimentalImportSupport:true
when @react-spring/native is loaded lazily inside a component render.
@changeset-bot

changeset-bot Bot commented Jun 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 4bf13b7

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 12 packages
Name Type
@react-spring/parallax Patch
@react-spring/core Patch
@react-spring/shared Patch
@react-spring/animated Patch
@react-spring/mock-raf Patch
@react-spring/rafz Patch
@react-spring/types Patch
@react-spring/konva Patch
@react-spring/native Patch
@react-spring/three Patch
@react-spring/web Patch
@react-spring/zdog Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@AndreiCalazans

Copy link
Copy Markdown
Author

Closing — wrong base branch. Re-opening against next.

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.

8 participants