Skip to content

Fix crash animated #51442

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed

Conversation

riteshshukla04
Copy link
Contributor

Summary:

Fixes #51395 .

Changelog:

[GENERAL][FIXED]Crash when Animated value & primitive value used together

Test Plan:

Can be tested on RNTester with

const myAnimatedValue = useAnimatedValue(100);
  return (
    <View style={styles.main}>
      <Animated.View style={[
        {width: 100, height: 100, backgroundColor: 'red'},
        {
          transform: [
            {translateX: myAnimatedValue},
          ],
        },
        {
          transform: [
            {translateY: 100},
          ],
        },

       ]} />
    </View>
  );

@facebook-github-bot facebook-github-bot added CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. labels May 18, 2025
@riteshshukla04
Copy link
Contributor Author

riteshshukla04 commented May 18, 2025

When an array of styles are passed its get flattened here

): ?AnimatedStyle {
const flatStyle = flattenStyle(inputStyle);
if (flatStyle == null) {

Now if array with animated object keys is passed to flattened style

[
        {width: 100, height: 100, backgroundColor: 'red'},
        {
          transform: [
            {translateX: myAnimatedValue2},
          ],
        },
        {
          transform: [
            {translateY: 100},
          ],
        },

       ]

The result is below

{
    "width": 100,
    "height": 100,
    "backgroundColor": "red",
    "transform": [
        {
            "translateY": 100
        }
    ]
}

It can be seen that {translateX: myAnimatedValue2} disappears completely .
Now when this flattened object is passed to

const [nodeKeys, nodes, style] = createAnimatedStyle(
flatStyle,
allowlist,
Platform.OS !== 'web',
);
if (nodes.length === 0) {
return null;

We don't recieve any animated nodes cause it doesn't exist in flattened array causing it to return null.

These tricks it into passing an style array(before flattening) with animated values to View component which causes this crash.
My fix here is to flatten style everytime if it is an array in animated component itself

Copy link

@Hardanish-Singh Hardanish-Singh left a comment

Choose a reason for hiding this comment

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

LGTM!

@facebook-github-bot
Copy link
Contributor

@rshest has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

Copy link
Member

@javache javache left a comment

Choose a reason for hiding this comment

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

Thanks for fixing this. Please add a unit test that shows how this behaviour previously broke.

Please also revert the changes to LogBoxInspectorSourceMapStatus

@riteshshukla04
Copy link
Contributor Author

Thanks so much for the review, @javache! 🙏 .
I'm a bit unsure about how to write a reliable Jest test case for this. I tried the following:

const tree =  await render.create(
        <Animated.View
          style={[
            {
              backgroundColor: 'red',
              width: 100,
              height: 100,
            },
            {transform: [{translateX: new Animated.Value(0)}]},
            [{transform: [{translateY: 100}]}],
          ]}
        />
         
      );
      const root = tree.toJSON();

      expect(root).toMatchSnapshot();
      

However, it seems to pass even in scenarios where the component crashes on device.
Should I add an example in RNTester instead?

Also we need to update the snapshot for as the current fix involves flattening style arrays passed to Animated. The snapshot for LogBoxInspectorSourceMapStatus still expects a nested style array, which no longer reflects the updated structure.

@javache
Copy link
Member

javache commented May 19, 2025

Instead of flattening, let's make sure all props passed through have values applied.

You won't see this in jest, since processTransform is only called at the mounting layer. You'd need to validate there's no more Animated.Value in the final props.

@riteshshukla04
Copy link
Contributor Author

Thanks so much for the response, @javache! 🙏

You're absolutely right — though from what I understand, validating all the props might not be very helpful in this case, since they’ll likely be overwritten anyway during the later stages when styles are further flattened (especially in the cases that are currently crashing).

Also, to properly apply all the style values when props are passed, it seems we would need to modify createAnimatedStyle itself to support working with style arrays directly, rather than flattened style object.

const flatStyle = flattenStyle(inputStyle);
if (flatStyle == null) {
return null;
}
const [nodeKeys, nodes, style] = createAnimatedStyle(
flatStyle,
allowlist,
Platform.OS !== 'web',
);

Please do correct me if I’ve misunderstood anything — happy to adjust the approach accordingly!

@riteshshukla04
Copy link
Contributor Author

Instead of flattening, let's make sure all props passed through have values applied.

You won't see this in jest, since processTransform is only called at the mounting layer. You'd need to validate there's no more Animated.Value in the final props.

Hey @javache , I was thinking about this yesterday night and now I think we can do this too .
Something like below will apply all the values and still preserve the array.

if (key === 'style' && Array.isArray(maybeNode)) {
       props[key] = maybeNode.map(style=>{
        const node = AnimatedStyle.from(style, undefined);
        if(node instanceof AnimatedStyle){
          return  node.__getValueWithStaticStyle(style);
        }
        return style;
       })
      }

Should we do something like this ? Whats your opinion?

@javache
Copy link
Member

javache commented May 20, 2025

I think the fix you're looking for needs to be in getValueWithStaticStyle (

__getValueWithStaticStyle(staticStyle: Object): Object | Array<Object> {
)

@javache
Copy link
Member

javache commented May 20, 2025

It's not super clear why we are flattening props there when we can see that in the end a series of unflattened props are passed through to the host component - that's incorrect.

@riteshshukla04
Copy link
Contributor Author

riteshshukla04 commented May 20, 2025

Hey @javache ,
__getValueWithStaticStyle wont be available unless node is an instance of Animated Style (which here is not as the animated values disappear after flattening) .
I can confirm that if we find a animated node then flattened props are passed to host component else non flattened.
The fix I was thinking here is either converting all the props to flattened or go through each element in style array and make the animated values apply before sending it to host component

@javache
Copy link
Member

javache commented May 20, 2025

__getValueWithStaticStyle wont be available unless node is an instance of Animated Style (which here is not as the animated values disappear after flattening) .

Good catch. Perhaps that's a bug that should be fixed here first? If there's an animated value in the unflattened style we should still allocate the Animated.Style.

cc @yungsters

@riteshshukla04
Copy link
Contributor Author

function createAnimatedStyle(
inputStyle: {[string]: mixed},
allowlist: ?AnimatedStyleAllowlist,
keepUnanimatedValues: boolean,
): [$ReadOnlyArray<string>, $ReadOnlyArray<AnimatedNode>, {[string]: mixed}] {
const nodeKeys: Array<string> = [];

I believe we may need to rewrite the function (and more support functions like __getValueWithStaticTransforms) to support non-flattened styles. However, I’m not entirely sure if addressing this edge case is worth the added complexity.

@riteshshukla04
Copy link
Contributor Author

I have updated this with new logic where I am not flattening the array but processing all the animated values (if found) . This should fix the issue without need for flattening before sending it to host component.
cc:- @javache

@javache
Copy link
Member

javache commented May 21, 2025

This needs a unit test to demonstrate the fix.

Wouldn't a better place for this fix be inside AnimatedStyle? Maybe removing just this line? https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/Animated/nodes/AnimatedStyle.js#L105

@riteshshukla04
Copy link
Contributor Author

riteshshukla04 commented May 21, 2025

Thanks for the reply @javache .
I initially tried removing that line but that makes style array flattened every time before sending to host component .
Also some test cases like these fail on that .

it('returns original `style` if it has no nodes', () => {
    const style = {color: 'red'};
    expect(getValue({style}).style).toBe(style);
  });

Should we go ahead with the suggested approach?

@riteshshukla04
Copy link
Contributor Author

riteshshukla04 commented May 29, 2025

@javache Can you please check if this can be merged . I have added test cases

@javache
Copy link
Member

javache commented May 29, 2025

I'll import this for the test-case - I think we can find a more generic solution.

@facebook-github-bot
Copy link
Contributor

@javache has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

yungsters added a commit to yungsters/react-native that referenced this pull request May 31, 2025
Summary:
When `Animated` traverses props for instances of `AnimatedNode`, it flattens `props.style` before traversing it so that we correctly ignore any `AnimatedNode` instances that may be shadowed by static values:

```
{
  style: [
    {transform: [{translateX: new Animated.Value(0)}]},
    {transform: [{translateX: 100}]},
  ],
}
```

However, there is a bug that occurs when *every* `AnimatedNode` instance is shadowed. In this case, `AnimatedProps` assumes that there are *no* `AnimatedNode` instances in the entire `props.style`.

It then incorrectly operates on the unflattened `props.style`, which *does* have `AnimatedNode` instances. When this is passed to `View`, the `AnimatedNode` instances are encountered and can cause a crash in `processTransform`, as reported by: facebook#51395

The fix for this was originally attempted by riteshshukla04 in facebook#51442. This diff reuses the same unit test case, but it applies a different fix that does not involve re-traversing the `props.style` object.

Changelog:
[General][Fixed] - Animated will no longer produce invalid `props.style` if every `AnimatedNode` instance is shadowed via style flattening.

Differential Revision: D75723284
@yungsters
Copy link
Contributor

Thanks for investigating this, sharing a detailed explanation of what you've found, and contributing a unit test case.

I took a look and put up a different fix that I think will have better performance and will be a bit more intuitive for maintainers: #51719

I verified that the unit test you contributed passes with it. If you have a chance, can you try it out to see if it addresses all of the problems you encountered?

@riteshshukla04
Copy link
Contributor Author

Thanks @yungsters . Your fix looks better . Closing this

yungsters added a commit to yungsters/react-native that referenced this pull request Jun 2, 2025
Summary:

When `Animated` traverses props for instances of `AnimatedNode`, it flattens `props.style` before traversing it so that we correctly ignore any `AnimatedNode` instances that may be shadowed by static values:

```
{
  style: [
    {transform: [{translateX: new Animated.Value(0)}]},
    {transform: [{translateX: 100}]},
  ],
}
```

However, there is a bug that occurs when *every* `AnimatedNode` instance is shadowed. In this case, `AnimatedProps` assumes that there are *no* `AnimatedNode` instances in the entire `props.style`.

It then incorrectly operates on the unflattened `props.style`, which *does* have `AnimatedNode` instances. When this is passed to `View`, the `AnimatedNode` instances are encountered and can cause a crash in `processTransform`, as reported by: facebook#51395

The fix for this was originally attempted by riteshshukla04 in facebook#51442. This diff reuses the same unit test case, but it applies a different fix that does not involve re-traversing the `props.style` object.

Changelog:
[General][Fixed] - Animated will no longer produce invalid `props.style` if every `AnimatedNode` instance is shadowed via style flattening.

Differential Revision: D75723284
yungsters added a commit to yungsters/react-native that referenced this pull request Jun 2, 2025
Summary:
Pull Request resolved: facebook#51719

When `Animated` traverses props for instances of `AnimatedNode`, it flattens `props.style` before traversing it so that we correctly ignore any `AnimatedNode` instances that may be shadowed by static values:

```
{
  style: [
    {transform: [{translateX: new Animated.Value(0)}]},
    {transform: [{translateX: 100}]},
  ],
}
```

However, there is a bug that occurs when *every* `AnimatedNode` instance is shadowed. In this case, `AnimatedProps` assumes that there are *no* `AnimatedNode` instances in the entire `props.style`.

It then incorrectly operates on the unflattened `props.style`, which *does* have `AnimatedNode` instances. When this is passed to `View`, the `AnimatedNode` instances are encountered and can cause a crash in `processTransform`, as reported by: facebook#51395

The fix for this was originally attempted by riteshshukla04 in facebook#51442. This diff reuses the same unit test case, but it applies a different fix that does not involve re-traversing the `props.style` object.

Changelog:
[General][Fixed] - Animated will no longer produce invalid `props.style` if every `AnimatedNode` instance is shadowed via style flattening.

Differential Revision: D75723284
yungsters added a commit to yungsters/react-native that referenced this pull request Jun 2, 2025
Summary:

When `Animated` traverses props for instances of `AnimatedNode`, it flattens `props.style` before traversing it so that we correctly ignore any `AnimatedNode` instances that may be shadowed by static values:

```
{
  style: [
    {transform: [{translateX: new Animated.Value(0)}]},
    {transform: [{translateX: 100}]},
  ],
}
```

However, there is a bug that occurs when *every* `AnimatedNode` instance is shadowed. In this case, `AnimatedProps` assumes that there are *no* `AnimatedNode` instances in the entire `props.style`.

It then incorrectly operates on the unflattened `props.style`, which *does* have `AnimatedNode` instances. When this is passed to `View`, the `AnimatedNode` instances are encountered and can cause a crash in `processTransform`, as reported by: facebook#51395

The fix for this was originally attempted by riteshshukla04 in facebook#51442. This diff reuses the same unit test case, but it applies a different fix that does not involve re-traversing the `props.style` object.

Changelog:
[General][Fixed] - Animated will no longer produce invalid `props.style` if every `AnimatedNode` instance is shadowed via style flattening.

Reviewed By: javache

Differential Revision: D75723284
yungsters added a commit to yungsters/react-native that referenced this pull request Jun 2, 2025
Summary:

When `Animated` traverses props for instances of `AnimatedNode`, it flattens `props.style` before traversing it so that we correctly ignore any `AnimatedNode` instances that may be shadowed by static values:

```
{
  style: [
    {transform: [{translateX: new Animated.Value(0)}]},
    {transform: [{translateX: 100}]},
  ],
}
```

However, there is a bug that occurs when *every* `AnimatedNode` instance is shadowed. In this case, `AnimatedProps` assumes that there are *no* `AnimatedNode` instances in the entire `props.style`.

It then incorrectly operates on the unflattened `props.style`, which *does* have `AnimatedNode` instances. When this is passed to `View`, the `AnimatedNode` instances are encountered and can cause a crash in `processTransform`, as reported by: facebook#51395

The fix for this was originally attempted by riteshshukla04 in facebook#51442. This diff reuses the same unit test case, but it applies a different fix that does not involve re-traversing the `props.style` object.

Changelog:
[General][Fixed] - Animated will no longer produce invalid `props.style` if every `AnimatedNode` instance is shadowed via style flattening.

Reviewed By: javache

Differential Revision: D75723284
yungsters added a commit to yungsters/react-native that referenced this pull request Jun 2, 2025
Summary:
Pull Request resolved: facebook#51719

When `Animated` traverses props for instances of `AnimatedNode`, it flattens `props.style` before traversing it so that we correctly ignore any `AnimatedNode` instances that may be shadowed by static values:

```
{
  style: [
    {transform: [{translateX: new Animated.Value(0)}]},
    {transform: [{translateX: 100}]},
  ],
}
```

However, there is a bug that occurs when *every* `AnimatedNode` instance is shadowed. In this case, `AnimatedProps` assumes that there are *no* `AnimatedNode` instances in the entire `props.style`.

It then incorrectly operates on the unflattened `props.style`, which *does* have `AnimatedNode` instances. When this is passed to `View`, the `AnimatedNode` instances are encountered and can cause a crash in `processTransform`, as reported by: facebook#51395

The fix for this was originally attempted by riteshshukla04 in facebook#51442. This diff reuses the same unit test case, but it applies a different fix that does not involve re-traversing the `props.style` object.

Changelog:
[General][Fixed] - Animated will no longer produce invalid `props.style` if every `AnimatedNode` instance is shadowed via style flattening.

Reviewed By: javache

Differential Revision: D75723284
yungsters added a commit to yungsters/react-native that referenced this pull request Jun 3, 2025
Summary:
Pull Request resolved: facebook#51719

When `Animated` traverses props for instances of `AnimatedNode`, it flattens `props.style` before traversing it so that we correctly ignore any `AnimatedNode` instances that may be shadowed by static values:

```
{
  style: [
    {transform: [{translateX: new Animated.Value(0)}]},
    {transform: [{translateX: 100}]},
  ],
}
```

However, there is a bug that occurs when *every* `AnimatedNode` instance is shadowed. In this case, `AnimatedProps` assumes that there are *no* `AnimatedNode` instances in the entire `props.style`.

It then incorrectly operates on the unflattened `props.style`, which *does* have `AnimatedNode` instances. When this is passed to `View`, the `AnimatedNode` instances are encountered and can cause a crash in `processTransform`, as reported by: facebook#51395

The fix for this was originally attempted by riteshshukla04 in facebook#51442. This diff reuses the same unit test case, but it applies a different fix that does not involve re-traversing the `props.style` object.

The fix is gated behind a feature flag, `alwaysFlattenAnimatedStyles`. This will enable us to validate correctness of the new behavior before enabling it for everyone. (Beyond fixing the bug described above, this also causes styles to flatten more aggressively, so production testing is important to ensure stability.)

Changelog:
[General][Changed] - Creates a feature flag that changes Animated to no longer produce invalid `props.style` if every `AnimatedNode` instance is shadowed via style flattening.

Reviewed By: javache

Differential Revision: D75723284
facebook-github-bot pushed a commit that referenced this pull request Jun 3, 2025
Summary:
Pull Request resolved: #51719

When `Animated` traverses props for instances of `AnimatedNode`, it flattens `props.style` before traversing it so that we correctly ignore any `AnimatedNode` instances that may be shadowed by static values:

```
{
  style: [
    {transform: [{translateX: new Animated.Value(0)}]},
    {transform: [{translateX: 100}]},
  ],
}
```

However, there is a bug that occurs when *every* `AnimatedNode` instance is shadowed. In this case, `AnimatedProps` assumes that there are *no* `AnimatedNode` instances in the entire `props.style`.

It then incorrectly operates on the unflattened `props.style`, which *does* have `AnimatedNode` instances. When this is passed to `View`, the `AnimatedNode` instances are encountered and can cause a crash in `processTransform`, as reported by: #51395

The fix for this was originally attempted by riteshshukla04 in #51442. This diff reuses the same unit test case, but it applies a different fix that does not involve re-traversing the `props.style` object.

The fix is gated behind a feature flag, `alwaysFlattenAnimatedStyles`. This will enable us to validate correctness of the new behavior before enabling it for everyone. (Beyond fixing the bug described above, this also causes styles to flatten more aggressively, so production testing is important to ensure stability.)

Changelog:
[General][Changed] - Creates a feature flag that changes Animated to no longer produce invalid `props.style` if every `AnimatedNode` instance is shadowed via style flattening.

Reviewed By: javache

Differential Revision: D75723284

fbshipit-source-id: 504f63e8edf836243d615783e119137a920ad271
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

crash when using multiple transform property with different value type : "Transform with key of "translateX" must be number or a percentage"
5 participants