Skip to content

Commit

Permalink
Fix warning about overwriting style with layout animation (#5644)
Browse files Browse the repository at this point in the history
## Summary
In this PR
#4969 I
didn't include neither animatedStyles nor styles nested in arrays.
That's why the problem wasn't reported in this example:
#5395

closes #5395 

|Before|After|
|---|---|
|<img width="387" alt="image"
src="https://github.com/software-mansion/react-native-reanimated/assets/56199675/52c1de46-49d8-458c-ac4b-659bbcbca531">|<img
width="387" alt="image"
src="https://github.com/software-mansion/react-native-reanimated/assets/56199675/d12eb7d6-b409-41b8-873b-bc8cbc46e258">|

## Test plan

<details><summary>Code</summary>

```jsx
import Animated, {
  useAnimatedStyle,
  SlideInDown,
  ZoomOut,
  useDerivedValue,
  withSpring,
  interpolate,
} from 'react-native-reanimated';
import { StyleSheet, View } from 'react-native';

import React, { useEffect, useState } from 'react';

interface Item {
  color: string;
}

function getRandomColor(): string {
  return '#' + Math.floor(Math.random() * 16777215).toString(16);
}

const Entering = SlideInDown.springify().mass(1).stiffness(1000).damping(500);
const Exiting = ZoomOut.springify().mass(1).stiffness(1000).damping(500);

function ItemComponent({ item, index }: { item: Item; index: number }) {
  const rotation = useDerivedValue(() => withSpring(index * 15), [index]);

  const animatedStyle = useAnimatedStyle(() => {
    return {
      zIndex: -index,
      backgroundColor: item.color,
      transform: [
        {
          translateX: interpolate(rotation.value, [0, 100], [0, 100]),
        },
        {
          translateY: interpolate(rotation.value, [0, 100], [0, -30]),
        },
        {
          scale: interpolate(rotation.value, [0, 100], [1, 0.7]),
        },
        {
          rotate: `${rotation.value}deg`,
        },
      ],
    };
  }, [rotation, index, item]);

  return (
    <Animated.View
      entering={Entering}
      exiting={Exiting}
      style={[styles.item, animatedStyle]}
    />
  );
}

export default function StackedLayoutAnimationExample() {
  const [items, setItems] = useState<Item[]>([
    { color: 'red' },
    { color: 'green' },
  ]);

  useEffect(() => {
    const interval = setInterval(() => {
      setItems((i) => [{ color: getRandomColor() }, ...i]);
    }, 1500);
    return () => clearInterval(interval);
  }, []);

  return (
    <View style={styles.container}>
      {items.map((item, index) => {
        if (index > 2) {
          return null;
        }
        return <ItemComponent key={item.color} item={item} index={index} />;
      })}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  item: {
    width: 100,
    height: 150,
    borderRadius: 15,
    position: 'absolute',
  },
});
```
</details>

---------

Co-authored-by: Aleksandra Cynk <aleksandracynk@Aleksandras-MacBook-Pro-3.local>
Co-authored-by: Tomek Zawadzki <tomasz.zawadzki@swmansion.com>
  • Loading branch information
3 people authored Feb 20, 2024
1 parent 34c377e commit 896be21
Showing 1 changed file with 50 additions and 15 deletions.
65 changes: 50 additions & 15 deletions src/animationBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
LayoutAnimationsValues,
} from './reanimated2/layoutReanimation';
import type { StyleProps } from './reanimated2/commonTypes';
import type { NestedArray } from './createAnimatedComponent/commonTypes';

const mockTargetValues: LayoutAnimationsValues = {
targetOriginX: 0,
Expand All @@ -25,12 +26,55 @@ const mockTargetValues: LayoutAnimationsValues = {
currentBorderRadius: 0,
};

function getCommonProperties(
layoutStyle: StyleProps,
componentStyle: StyleProps | Array<StyleProps>
) {
let componentStyleFlat = Array.isArray(componentStyle)
? componentStyle.flat()
: [componentStyle];

componentStyleFlat = componentStyleFlat.map((style) =>
'initial' in style
? style.initial.value // Include properties of animated style
: style
);

const componentStylesKeys = componentStyleFlat.flatMap((style) =>
Object.keys(style)
);

const commonKeys = Object.keys(layoutStyle).filter((key) =>
componentStylesKeys.includes(key)
);

return commonKeys;
}

function maybeReportOverwrittenProperties(
layoutAnimationStyle: StyleProps,
style: NestedArray<StyleProps>,
displayName: string
) {
const commonProperties = getCommonProperties(layoutAnimationStyle, style);

if (commonProperties.length > 0) {
console.warn(
`[Reanimated] ${
commonProperties.length === 1 ? 'Property' : 'Properties'
} "${commonProperties.join(
', '
)}" of ${displayName} may be overwritten by a layout animation. Please wrap your component with an animated view and apply the layout animation on the wrapper.`
);
}
}

export function maybeBuild(
layoutAnimationOrBuilder:
| ILayoutAnimationBuilder
| LayoutAnimationFunction
| Keyframe,
style: StyleProps | undefined,
style: NestedArray<StyleProps> | undefined,
displayName: string
): LayoutAnimationFunction | Keyframe {
const isAnimationBuilder = (
Expand All @@ -42,21 +86,12 @@ export function maybeBuild(
if (isAnimationBuilder(layoutAnimationOrBuilder)) {
const animationFactory = layoutAnimationOrBuilder.build();
const layoutAnimation = animationFactory(mockTargetValues);
const animatedStyle = layoutAnimation.animations;

const getCommonProperties = (obj1: object, obj2: object) =>
Object.keys(obj1).filter((key) =>
Object.prototype.hasOwnProperty.call(obj2, key)
);

const commonProperties = getCommonProperties(animatedStyle, style || {});
if (commonProperties.length > 0) {
console.warn(
`[Reanimated] ${
commonProperties.length === 1 ? 'Property' : 'Properties: '
} "${commonProperties.join(
', '
)}" of ${displayName} may be overwritten with layout animation. Please create a wrapper with the layout animation you want to apply.`
if (__DEV__ && style) {
maybeReportOverwrittenProperties(
layoutAnimation.animations,
style,
displayName
);
}

Expand Down

0 comments on commit 896be21

Please sign in to comment.