react-doctor/rn-animate-layout-property
Animate `transform: [{ translateX/Y }, { scale }]` and `opacity` instead of layout props — layout runs on the JS thread; transform/opacity run on the GPU compositor
- Category: React Native
- Severity: error
- Source:
oxlint-plugin-react-doctor - Framework: react-native
- Enabled when: framework=react-native and capabilities=react-native
Validation prompt
Use this to decide whether a fired diagnostic is real or a false positive.
Confirm an animated property inside useAnimatedStyle is a layout-affecting key (width, height, top, left, right, bottom, min/max width/height, margin*, padding*, flex*) — each frame forces a layout pass on the JS thread. False positive: a static layout key sitting next to an animated transform isn't problematic unless the layout key itself is driven by a shared value or withTiming/withSpring.
Fix prompt
Use this once validation confirms the diagnostic is real.
Animate transform: [{ translateX }, { translateY }, { scale }] and opacity on an Animated.View instead — both stay on the UI thread compositor. If size really must animate, scale a parent of fixed dimensions rather than animating width/height directly. See https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started
Related rules
More React Native rules from the rules reference:
react-doctor/rn-animation-reaction-as-derived: Replace useAnimatedReaction with `useDerivedValue(() => ..., [deps])` — shorter, native dependency tracking, no side-effect implicationreact-doctor/rn-bottom-sheet-prefer-native: Use `<Modal presentationStyle="formSheet">` (RN v7+) for native gesture handling and snap pointsreact-doctor/rn-detox-missing-await: Prepend await to the Detox action, waitFor chain, or expect(element(...)) assertion: await element(by.id('submit')).tap()react-doctor/rn-list-callback-per-row: Hoist the handler with useCallback at list scope and pass the row id as a primitive prop, so the row's memo() shallow-compare actually hitsreact-doctor/rn-list-data-mapped: Wrap the projection in `useMemo(() => items.map(...), [items])` so the list's `data` prop has a stable reference across parent renders