From b0314fda069ac655160abc0aed51001bb4309671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=81opaci=C5=84ski?= Date: Wed, 10 Dec 2025 00:29:13 +0000 Subject: [PATCH 01/10] Add support for nested animated props objects, fix incorrect cleanup of animated props arrays --- .../__typetests__/common/useAnimatedPropsTest.tsx | 14 ++++++++++++++ .../createAnimatedComponent/AnimatedComponent.tsx | 4 ++-- .../src/createAnimatedComponent/commonTypes.ts | 2 +- .../react-native-reanimated/src/helperTypes.ts | 11 ++++------- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/packages/react-native-reanimated/__typetests__/common/useAnimatedPropsTest.tsx b/packages/react-native-reanimated/__typetests__/common/useAnimatedPropsTest.tsx index 916e687e1630..ca61127ac65a 100644 --- a/packages/react-native-reanimated/__typetests__/common/useAnimatedPropsTest.tsx +++ b/packages/react-native-reanimated/__typetests__/common/useAnimatedPropsTest.tsx @@ -177,4 +177,18 @@ function UseAnimatedPropsTest() { }; return ; } + + function UseAnimatedPropsNestedArrays() { + const animatedProps = useAnimatedProps(() => ({ + pointerEvents: 'none' as const, + })); + const cssProps: CSSStyle = { + animationName: { to: { backgroundColor: 'green' } }, + }; + return ( + + ); + } } diff --git a/packages/react-native-reanimated/src/createAnimatedComponent/AnimatedComponent.tsx b/packages/react-native-reanimated/src/createAnimatedComponent/AnimatedComponent.tsx index f89bef3ca728..42642428755b 100644 --- a/packages/react-native-reanimated/src/createAnimatedComponent/AnimatedComponent.tsx +++ b/packages/react-native-reanimated/src/createAnimatedComponent/AnimatedComponent.tsx @@ -227,8 +227,8 @@ export default class AnimatedComponent for (const style of this._animatedStyles) { style.viewDescriptors.remove(viewTag); } - if (this.props.animatedProps?.viewDescriptors) { - this.props.animatedProps.viewDescriptors.remove(viewTag); + for (const animatedProp of this._animatedProps) { + animatedProp?.viewDescriptors?.remove(viewTag); } } } diff --git a/packages/react-native-reanimated/src/createAnimatedComponent/commonTypes.ts b/packages/react-native-reanimated/src/createAnimatedComponent/commonTypes.ts index 18537c14996d..a6476daf73e5 100644 --- a/packages/react-native-reanimated/src/createAnimatedComponent/commonTypes.ts +++ b/packages/react-native-reanimated/src/createAnimatedComponent/commonTypes.ts @@ -82,7 +82,7 @@ export type AnimatedComponentProps< > = P & { ref?: Ref; style?: NestedArray; - animatedProps?: Partial>; + animatedProps?: NestedArray>>; jestAnimatedValues?: RefObject; animatedStyle?: StyleProps; sharedTransitionTag?: string; diff --git a/packages/react-native-reanimated/src/helperTypes.ts b/packages/react-native-reanimated/src/helperTypes.ts index b29c7abafc83..d0697c1f5f30 100644 --- a/packages/react-native-reanimated/src/helperTypes.ts +++ b/packages/react-native-reanimated/src/helperTypes.ts @@ -16,7 +16,7 @@ import type { SharedValue, } from './commonTypes'; import type { CSSStyle } from './css'; -import type { AddArrayPropertyType } from './css/types'; +import type { NestedArray } from './createAnimatedComponent/commonTypes'; import type { BaseAnimationBuilder } from './layoutReanimation/animationBuilder/BaseAnimationBuilder'; import type { ReanimatedKeyframe } from './layoutReanimation/animationBuilder/Keyframe'; import type { SharedTransition } from './layoutReanimation/SharedTransition'; @@ -94,10 +94,6 @@ type SharedTransitionProps = { sharedTransitionStyle?: SharedTransition; }; -type AnimatedPropsProp = RestProps & - AnimatedStyleProps & - LayoutProps; - export type AnimatedProps = RestProps & AnimatedStyleProps & LayoutProps & { @@ -106,8 +102,9 @@ export type AnimatedProps = RestProps & * * @see https://docs.swmansion.com/react-native-reanimated/docs/core/useAnimatedProps */ - animatedProps?: AddArrayPropertyType< - Partial> | CSSStyle + animatedProps?: NestedArray< + Partial & AnimatedStyleProps & LayoutProps> | + CSSStyle >; } & SharedTransitionProps; From d23ce7dab77d4b638f5069801807a62d382d6d91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=81opaci=C5=84ski?= Date: Thu, 11 Dec 2025 22:57:45 +0000 Subject: [PATCH 02/10] Add correct animatedProps prop type and more typetests --- .../common/useAnimatedPropsTest.tsx | 62 +++++++++++-------- .../src/helperTypes.ts | 13 ++-- 2 files changed, 44 insertions(+), 31 deletions(-) diff --git a/packages/react-native-reanimated/__typetests__/common/useAnimatedPropsTest.tsx b/packages/react-native-reanimated/__typetests__/common/useAnimatedPropsTest.tsx index ca61127ac65a..4be35c390395 100644 --- a/packages/react-native-reanimated/__typetests__/common/useAnimatedPropsTest.tsx +++ b/packages/react-native-reanimated/__typetests__/common/useAnimatedPropsTest.tsx @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/no-unused-vars */ import React from 'react'; -import type { FlatListProps } from 'react-native'; +import type { FlatListProps, ViewProps } from 'react-native'; import { FlatList } from 'react-native'; import type { CSSStyle } from '../..'; @@ -63,10 +63,6 @@ function UseAnimatedPropsTest() { const optionalProps = useAnimatedProps>(() => ({ style: {}, })); - const requiredProps = useAnimatedProps>(() => ({ - data: ['1'], - renderItem: () => null, - })); // Should pass because required props are set. return ( @@ -149,45 +145,61 @@ function UseAnimatedPropsTest() { } function UseAnimatedPropsTestMultiple1() { - const animatedProps1 = useAnimatedProps(() => ({ - pointerEvents: 'none' as const, - })); - const animatedProps2 = useAnimatedProps(() => ({ - pointerEvents: 'auto' as const, - })); + const animatedProps1 = useAnimatedProps(() => ({ pointerEvents: 'none' as const })); + const animatedProps2 = useAnimatedProps(() => ({ pointerEvents: 'auto' as const })); return ; } function UseAnimatedPropsMultiple2() { - const cssProps1: CSSStyle = { - animationName: { from: { backgroundColor: 'red' } }, + const cssProps1: CSSStyle = { + animationName: { from: { pointerEvents: 'auto' } }, }; - const cssProps2: CSSStyle = { - animationName: { to: { backgroundColor: 'blue' } }, + const cssProps2: CSSStyle = { + animationName: { to: { pointerEvents: 'none' } }, }; return ; } function UseAnimatedPropsMultiple3() { - const animatedProps = useAnimatedProps(() => ({ - pointerEvents: 'none' as const, - })); - const cssProps: CSSStyle = { - animationName: { to: { backgroundColor: 'blue' } }, + const animatedProps = useAnimatedProps( + () => ({ pointerEvents: 'none' as const }) + ); + const cssProps: CSSStyle = { + animationName: { to: { pointerEvents: 'auto' } }, }; return ; } function UseAnimatedPropsNestedArrays() { - const animatedProps = useAnimatedProps(() => ({ - pointerEvents: 'none' as const, + const animatedProps = useAnimatedProps( + () => ({ pointerEvents: 'none' as const }) + ); + const cssProps: CSSStyle = { + animationName: { + from: { pointerEvents: 'auto' }, + to: { pointerEvents: 'none' }, + }, + }; + const accessibilityProps = useAnimatedProps(() => ({ + accessibilityLiveRegion: 'polite' as const, })); - const cssProps: CSSStyle = { - animationName: { to: { backgroundColor: 'green' } }, + return ( + + ); + } + + function UseAnimatedPropsDeeplyNestedArrays() { + const cssFrom: CSSStyle = { + animationName: { from: { pointerEvents: 'auto' } }, + }; + const cssTo: CSSStyle = { + animationName: { to: { pointerEvents: 'none' } }, }; return ( ); } diff --git a/packages/react-native-reanimated/src/helperTypes.ts b/packages/react-native-reanimated/src/helperTypes.ts index d0697c1f5f30..bd1db55ed735 100644 --- a/packages/react-native-reanimated/src/helperTypes.ts +++ b/packages/react-native-reanimated/src/helperTypes.ts @@ -15,7 +15,6 @@ import type { LayoutAnimationFunction, SharedValue, } from './commonTypes'; -import type { CSSStyle } from './css'; import type { NestedArray } from './createAnimatedComponent/commonTypes'; import type { BaseAnimationBuilder } from './layoutReanimation/animationBuilder/BaseAnimationBuilder'; import type { ReanimatedKeyframe } from './layoutReanimation/animationBuilder/Keyframe'; @@ -47,8 +46,13 @@ type AnimatedStyleProps = { }; /** Component props that are not specially handled by us. */ +type ComponentPropsWithoutStyle = Omit< + Props, + keyof PickStyleProps | 'style' +>; + type RestProps = { - [K in keyof Omit | 'style'>]: + [K in keyof ComponentPropsWithoutStyle]: | Props[K] | SharedValue; }; @@ -102,10 +106,7 @@ export type AnimatedProps = RestProps & * * @see https://docs.swmansion.com/react-native-reanimated/docs/core/useAnimatedProps */ - animatedProps?: NestedArray< - Partial & AnimatedStyleProps & LayoutProps> | - CSSStyle - >; + animatedProps?: NestedArray>>; } & SharedTransitionProps; // THE LAND OF THE DEPRECATED From 8b031f0b863836ff4cacba7ebba964e22ed52966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=81opaci=C5=84ski?= Date: Fri, 12 Dec 2025 00:19:22 +0100 Subject: [PATCH 03/10] Add unit tests for to ensure that multiple animated props objects work properly --- .../__snapshots__/animatedProps.test.tsx.snap | 199 ++++++++++++- .../__tests__/animatedProps.test.tsx | 264 ++++++++++++++++-- .../common/useAnimatedPropsTest.tsx | 20 +- 3 files changed, 446 insertions(+), 37 deletions(-) diff --git a/packages/react-native-reanimated/__tests__/__snapshots__/animatedProps.test.tsx.snap b/packages/react-native-reanimated/__tests__/__snapshots__/animatedProps.test.tsx.snap index 978580201eb0..30e63c7ac4dd 100644 --- a/packages/react-native-reanimated/__tests__/__snapshots__/animatedProps.test.tsx.snap +++ b/packages/react-native-reanimated/__tests__/__snapshots__/animatedProps.test.tsx.snap @@ -1,6 +1,201 @@ // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing -exports[`animatedProps Custom animated component 1`] = ` +exports[`animatedProps SVG component SVG component cannot be tested 1`] = ` + + + + + + + + + + Click me + + + + +`; + +exports[`animatedProps TextInput component Multiple animated props objects matches snapshot 1`] = ` + + + + + + Click me + + + + +`; + +exports[`animatedProps TextInput component Single animated props object matches snapshot 1`] = ` diff --git a/packages/react-native-reanimated/__tests__/animatedProps.test.tsx b/packages/react-native-reanimated/__tests__/animatedProps.test.tsx index 6838bfcaaa7b..8d2b99df8085 100644 --- a/packages/react-native-reanimated/__tests__/animatedProps.test.tsx +++ b/packages/react-native-reanimated/__tests__/animatedProps.test.tsx @@ -1,35 +1,32 @@ import { fireEvent, render } from '@testing-library/react-native'; +import type { ComponentProps } from 'react'; import React from 'react'; import { Button, TextInput, View } from 'react-native'; import Animated, { useAnimatedProps, useSharedValue, + withTiming, } from 'react-native-reanimated'; +import { Circle, Svg } from 'react-native-svg'; const animationDuration = 100; +const AnimatedCircle = Animated.createAnimatedComponent(Circle); const AnimatedTextInput = Animated.createAnimatedComponent(TextInput); -export default function AnimatedComponent() { - const r = useSharedValue(20); - const width = useSharedValue(20); - - const handlePress = () => { - r.value += 10; - width.value += 10; - }; - - const textAnimatedProps = useAnimatedProps(() => { - return { - text: `Box width: ${width.value}`, - defaultValue: `Box width: ${width.value}`, - }; - }); +interface BaseTextInputComponentProps { + animatedProps: ComponentProps['animatedProps']; + onPress?: () => void; +} +function BaseTextInputComponent({ + animatedProps, + onPress, +}: BaseTextInputComponentProps) { return ( - -