animated() throws 'cannot add a new property' on non-extensible React Native host components (Hermes)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 29.1k
- Forks
- 1.2k
- PR merge metrics
- No merged PRs in 30d
Description
Bug
@react-spring/animated's createHost caches animated wrappers by writing directly to the component object:
Component[cacheKey] = withAnimated(Component, hostConfig)
On Hermes, React Native host components (View, Text, Image) become non-extensible after their first JSX render. In strict mode this throws TypeError: cannot add a new property, which crashes the module initialization.
Reproduction
- React Native with Hermes
- Metro config:
inlineRequires: true+experimentalImportSupport: true - Any component that uses
@react-spring/nativeloaded lazily (inside a render)
With inlined requires, @react-spring/native is first required during a component render — by that point Hermes has already rendered the host components via JSX elsewhere and locked their shapes. createHost runs at module scope, iterates { View, Text, Image }, and throws on the first Component[cacheKey] = ... assignment.
Metro's guardedLoadModule swallows the error and returns undefined for the module, causing a subsequent TypeError: Cannot read property 'useSpring' of undefined.
Call stack (from device)
animated react-spring_animated.development.cjs:356
<anonymous> react-spring_animated.development.cjs:365
eachProp react-spring_shared.development.cjs:120
createHost react-spring_animated.development.cjs:361
<global> react-spring_native.development.cjs:90
loadModuleImplementation require.js:285
guardedLoadModule
Fix
Use the direct property write as the fast path, catch the TypeError, and fall back to a module-level WeakMap:
const fallbackCache = new WeakMap<object, any>()
// inside animated():
let cached = Component[cacheKey] ?? fallbackCache.get(Component)
if (!cached) {
cached = withAnimated(Component, hostConfig)
try {
Component[cacheKey] = cached
} catch {
// non-extensible component (e.g. Hermes host component)
}
fallbackCache.set(Component, cached)
}
Component = cached
PR with fix: https://github.com/AndreiCalazans/react-spring/pull/new/fix/non-extensible-component-cache
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at createHost in @react-spring/animated, especially the animated() cache path, and reproduce with @react-spring/native under Hermes using the stated Metro settings. Verify that lazy loading no longer throws for non-extensible View, Text, and Image host components and that useSpring remains available after loading.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react-native, typescript
- Domain
- mobile
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100