Skip to content

Commit

Permalink
Make animated components use different tags for events (#5960)
Browse files Browse the repository at this point in the history
## Summary

Turns out the usual component tag (used for Layout Animations, Shared
Element Transitions and Animated Styles) won't always work for events if
the component with nested event emitters is made animated. Thus I am
splitting these two into different tag variables.

## Test plan

You can check out the following code (logs in console): 
<details><summary>Code</summary>

``` TYPESCRIPT
import React from 'react';
import Animated, { useAnimatedScrollHandler } from 'react-native-reanimated';
import { StyleSheet, View, Text } from 'react-native';
import { FlashList } from '@shopify/flash-list';

const AnimatedFlashList = Animated.createAnimatedComponent(FlashList);

const data = new Array(45);

export default function EmptyApp() {
  const scrollHandler = useAnimatedScrollHandler(
    {
      onBeginDrag: () => {
        'worklet';
        console.log('ON BEGIN DRAG');
      },
      onScroll: () => {
        'worklet';
        console.log('ON SCROLL');
      },
      onMomentumEnd: () => {
        'worklet';
        console.log('ON END');
      },
    },
    []
  );

  const renderFlashListItem = () => {
    return <View style={styles.itemFlash} />;
  };

  const renderFlatListItem = () => {
    return <View style={styles.itemFlat} />;
  };

  return (
    <View style={styles.container}>
      <View style={styles.listContainer}>
        <Text>Animated FlashList</Text>
        <AnimatedFlashList
          onScroll={scrollHandler}
          renderItem={renderFlashListItem}
          data={data}
          estimatedItemSize={40}
        />
      </View>
      <View style={styles.listContainer}>
        <Text>Animated FlatList</Text>
        <Animated.FlatList
          onScroll={scrollHandler}
          renderItem={renderFlatListItem}
          data={data}
        />
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'row',
  },
  listContainer: {
    flex: 1,
    flexDirection: 'column',
  },
  itemFlash: {
    width: 20,
    height: 20,
    backgroundColor: 'red',
    margin: 10,
  },
  itemFlat: {
    width: 20,
    height: 20,
    backgroundColor: 'green',
    margin: 10,
  },
});

```
</details>

Current tests work on:

- [x] Paper
- [x] Fabric
- [x] Web
  • Loading branch information
szydlovsky committed May 8, 2024
1 parent 7c5500f commit deb7825
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 26 deletions.
3 changes: 2 additions & 1 deletion src/createAnimatedComponent/commonTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ export interface AnimatedComponentRef extends Component {
export interface IAnimatedComponentInternal {
_styles: StyleProps[] | null;
_animatedProps?: Partial<AnimatedComponentProps<AnimatedProps>>;
_viewTag: number;
_componentViewTag: number;
_eventViewTag: number;
_isFirstRender: boolean;
jestAnimatedStyle: { value: StyleProps };
_component: AnimatedComponentRef | HTMLElement | null;
Expand Down
75 changes: 50 additions & 25 deletions src/createAnimatedComponent/createAnimatedComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ export function createAnimatedComponent(
{
_styles: StyleProps[] | null = null;
_animatedProps?: Partial<AnimatedComponentProps<AnimatedProps>>;
_viewTag = -1;
_componentViewTag = -1;
_eventViewTag = -1;
_isFirstRender = true;
jestAnimatedStyle: { value: StyleProps } = { value: {} };
_component: AnimatedComponentRef | HTMLElement | null = null;
Expand All @@ -143,7 +144,8 @@ export function createAnimatedComponent(
}

componentDidMount() {
this._viewTag = this._getViewInfo().viewTag as number;
this._setComponentViewTag();
this._setEventViewTag();
this._attachNativeEvents();
this._jsPropsUpdater.addOnJSPropsChangeListener(this);
this._attachAnimatedStyles();
Expand Down Expand Up @@ -185,7 +187,10 @@ export function createAnimatedComponent(
if (this.props.sharedTransitionTag) {
this._configureSharedTransition(true);
}
this._sharedElementTransition?.unregisterTransition(this._viewTag, true);
this._sharedElementTransition?.unregisterTransition(
this._componentViewTag,
true
);

const exiting = this.props.exiting;
if (
Expand All @@ -209,7 +214,7 @@ export function createAnimatedComponent(
: getReduceMotionFromConfig();
if (!reduceMotionInExiting) {
updateLayoutAnimations(
this._viewTag,
this._componentViewTag,
LayoutAnimationType.EXITING,
maybeBuild(
exiting,
Expand All @@ -221,12 +226,22 @@ export function createAnimatedComponent(
}
}

_getEventViewRef() {
// Make sure to get the scrollable node for components that implement
// `ScrollResponder.Mixin`.
return (this._component as AnimatedComponentRef)?.getScrollableNode
? (this._component as AnimatedComponentRef).getScrollableNode?.()
: this._component;
_setComponentViewTag() {
this._componentViewTag = this._getViewInfo().viewTag as number;
}

_setEventViewTag() {
// Setting the tag for registering events - since the event emitting view can be nested inside the main component
const componentAnimatedRef = this._component as AnimatedComponentRef;
if (componentAnimatedRef.getScrollableNode) {
const scrollableNode = componentAnimatedRef.getScrollableNode();
this._eventViewTag = findNodeHandle(scrollableNode) ?? -1;
} else {
this._eventViewTag =
findNodeHandle(
options?.setNativeProps ? this : componentAnimatedRef
) ?? -1;
}
}

_attachNativeEvents() {
Expand All @@ -236,7 +251,7 @@ export function createAnimatedComponent(
has('workletEventHandler', prop) &&
prop.workletEventHandler instanceof WorkletEventHandler
) {
prop.workletEventHandler.registerForEvents(this._viewTag, key);
prop.workletEventHandler.registerForEvents(this._eventViewTag, key);
}
}
}
Expand All @@ -248,7 +263,7 @@ export function createAnimatedComponent(
has('workletEventHandler', prop) &&
prop.workletEventHandler instanceof WorkletEventHandler
) {
prop.workletEventHandler.unregisterFromEvents(this._viewTag);
prop.workletEventHandler.unregisterFromEvents(this._eventViewTag);
}
}
}
Expand All @@ -258,15 +273,17 @@ export function createAnimatedComponent(
for (const style of this._styles) {
style.viewsRef.remove(this);
}
} else if (this._viewTag !== -1 && this._styles !== null) {
} else if (this._componentViewTag !== -1 && this._styles !== null) {
for (const style of this._styles) {
style.viewDescriptors.remove(this._viewTag);
style.viewDescriptors.remove(this._componentViewTag);
}
if (this.props.animatedProps?.viewDescriptors) {
this.props.animatedProps.viewDescriptors.remove(this._viewTag);
this.props.animatedProps.viewDescriptors.remove(
this._componentViewTag
);
}
if (isFabric()) {
removeFromPropsRegistry(this._viewTag);
removeFromPropsRegistry(this._componentViewTag);
}
}
}
Expand All @@ -283,15 +300,19 @@ export function createAnimatedComponent(
const newProp = this.props[key];
if (!newProp) {
// Prop got deleted
prevProp.workletEventHandler.unregisterFromEvents(this._viewTag);
prevProp.workletEventHandler.unregisterFromEvents(
this._eventViewTag
);
} else if (
has('workletEventHandler', newProp) &&
newProp.workletEventHandler instanceof WorkletEventHandler &&
newProp.workletEventHandler !== prevProp.workletEventHandler
) {
// Prop got changed
prevProp.workletEventHandler.unregisterFromEvents(this._viewTag);
newProp.workletEventHandler.registerForEvents(this._viewTag);
prevProp.workletEventHandler.unregisterFromEvents(
this._eventViewTag
);
newProp.workletEventHandler.registerForEvents(this._eventViewTag);
}
}
}
Expand All @@ -304,7 +325,7 @@ export function createAnimatedComponent(
!prevProps[key]
) {
// Prop got added
newProp.workletEventHandler.registerForEvents(this._viewTag);
newProp.workletEventHandler.registerForEvents(this._eventViewTag);
}
}
}
Expand Down Expand Up @@ -381,7 +402,7 @@ export function createAnimatedComponent(
adaptViewConfig(viewConfig);
}

this._viewTag = viewTag as number;
this._componentViewTag = viewTag as number;

// remove old styles
if (prevStyles) {
Expand Down Expand Up @@ -487,7 +508,11 @@ export function createAnimatedComponent(
AnimatedComponent.displayName
)
: undefined;
updateLayoutAnimations(this._viewTag, LayoutAnimationType.LAYOUT, layout);
updateLayoutAnimations(
this._componentViewTag,
LayoutAnimationType.LAYOUT,
layout
);
}

_configureSharedTransition(isUnmounting = false) {
Expand All @@ -497,7 +522,7 @@ export function createAnimatedComponent(
const { sharedTransitionTag } = this.props;
if (!sharedTransitionTag) {
this._sharedElementTransition?.unregisterTransition(
this._viewTag,
this._componentViewTag,
isUnmounting
);
this._sharedElementTransition = null;
Expand All @@ -508,7 +533,7 @@ export function createAnimatedComponent(
this._sharedElementTransition ??
new SharedTransition();
sharedElementTransition.registerTransition(
this._viewTag,
this._componentViewTag,
sharedTransitionTag,
isUnmounting
);
Expand All @@ -527,7 +552,7 @@ export function createAnimatedComponent(
? (ref as HTMLElement)
: findNodeHandle(ref as Component);

this._viewTag = tag as number;
this._componentViewTag = tag as number;

const { layout, entering, exiting, sharedTransitionTag } = this.props;
if (
Expand Down

0 comments on commit deb7825

Please sign in to comment.