Skip to content

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

Open
AndreiCalazans wants to merge 3 commits into
pmndrs:nextfrom
AndreiCalazans:fix/non-extensible-component-cache-v2
Open

fix(animated): WeakMap fallback for non-extensible component caching#2535
AndreiCalazans wants to merge 3 commits into
pmndrs:nextfrom
AndreiCalazans:fix/non-extensible-component-cache-v2

Conversation

@AndreiCalazans

@AndreiCalazans AndreiCalazans commented Jun 9, 2026

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
   }
 }

Context

This was found while enabling Metro's inlineRequires: true + experimentalImportSupport: true in a large React Native monorepo. A local Yarn patch on @react-spring/animated@10.0.3 with this exact fix is already shipping in production while we wait for an upstream release: https://coinbase.ghe.com/consumer/react-native/pull/new/acalazans/metro-inline-requires-enable

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: c211263

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/animated Patch
@react-spring/core Patch
@react-spring/mock-raf Patch
@react-spring/parallax Patch
@react-spring/rafz Patch
@react-spring/shared 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

@joshuaellis joshuaellis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

it'd be great to get a failing test for this & a changeset.

joshuaellis
joshuaellis previously approved these changes Jun 9, 2026
@joshuaellis

Copy link
Copy Markdown
Member

Looks like there's some TS errors that need to be solved.

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.

animated() throws 'cannot add a new property' on non-extensible React Native host components (Hermes)

2 participants