Skip to content
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

Make animated component's event tag properly update #6030

Merged
merged 10 commits into from
Jun 11, 2024
118 changes: 118 additions & 0 deletions src/createAnimatedComponent/NativeEventsManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
'use strict';
import type {
INativeEventsManager,
IAnimatedComponentInternal,
AnimatedComponentProps,
InitialComponentProps,
} from './commonTypes';
import { has } from './utils';
import { WorkletEventHandler } from '../reanimated2/WorkletEventHandler';
import { isWeb } from '../reanimated2/PlatformChecker';

const IS_WEB = isWeb();

type ManagedAnimatedComponent = React.Component<
szydlovsky marked this conversation as resolved.
Show resolved Hide resolved
AnimatedComponentProps<InitialComponentProps>
> &
IAnimatedComponentInternal;

type WorkletEventHandlerProp = {
szydlovsky marked this conversation as resolved.
Show resolved Hide resolved
workletEventHandler: InstanceType<typeof WorkletEventHandler>;
};

function isWorkletEventHandler(prop: unknown): prop is WorkletEventHandlerProp {
return (
has('workletEventHandler', prop) &&
prop.workletEventHandler instanceof WorkletEventHandler
);
}

function executeForEachEventHandler(
props: AnimatedComponentProps<InitialComponentProps>,
callback: (
key: string,
handler: InstanceType<typeof WorkletEventHandler>
) => void
) {
for (const key in props) {
const prop = props[key];
if (isWorkletEventHandler(prop)) {
callback(key, prop.workletEventHandler);
}
}
}
szydlovsky marked this conversation as resolved.
Show resolved Hide resolved

export class NativeEventsManager implements INativeEventsManager {
_managedComponent: ManagedAnimatedComponent;

constructor(component: ManagedAnimatedComponent) {
this._managedComponent = component;
}

public attachNativeEvents(): void {
if (IS_WEB) {
return;
}
executeForEachEventHandler(this._managedComponent.props, (key, handler) => {
handler.registerForEvents(this._managedComponent._eventViewTag, key);
});
}
szydlovsky marked this conversation as resolved.
Show resolved Hide resolved

public detachNativeEvents(): void {
if (IS_WEB) {
return;
}
executeForEachEventHandler(
this._managedComponent.props,
(_key, handler) => {
handler.unregisterFromEvents(this._managedComponent._eventViewTag);
}
);
}

public updateNativeEvents(
prevProps: AnimatedComponentProps<InitialComponentProps>,
computedEventTag: number
): void {
if (IS_WEB) {
return;
}
// If the event view tag changes, we need to completely re-mount all events
if (this._managedComponent._eventViewTag !== computedEventTag) {
// Remove all bindings from previous props that ran on the old viewTag
executeForEachEventHandler(prevProps, (_key, handler) => {
handler.unregisterFromEvents(this._managedComponent._eventViewTag);
});
// We don't need to unregister from current (new) props, because their events weren't registered yet
// Replace the view tag
this._managedComponent._eventViewTag = computedEventTag;
szydlovsky marked this conversation as resolved.
Show resolved Hide resolved
// Attach the events with a new viewTag
this.attachNativeEvents();
return;
}

executeForEachEventHandler(prevProps, (key, prevHandler) => {
const newProp = this._managedComponent.props[key];
if (!newProp) {
// Prop got deleted
prevHandler.unregisterFromEvents(this._managedComponent._eventViewTag);
} else if (
isWorkletEventHandler(newProp) &&
newProp.workletEventHandler !== prevHandler
) {
// Prop got changed
prevHandler.unregisterFromEvents(this._managedComponent._eventViewTag);
newProp.workletEventHandler.registerForEvents(
this._managedComponent._eventViewTag
);
}
});

executeForEachEventHandler(this._managedComponent.props, (key, handler) => {
if (!prevProps[key]) {
// Prop got added
handler.registerForEvents(this._managedComponent._eventViewTag);
}
});
}
}
10 changes: 10 additions & 0 deletions src/createAnimatedComponent/commonTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ export interface IJSPropsUpdater {
): void;
}

export interface INativeEventsManager {
attachNativeEvents(): void;
detachNativeEvents(): void;
updateNativeEvents(
prevProps: AnimatedComponentProps<InitialComponentProps>,
computedEventTag: number
): void;
}

export type LayoutAnimationStaticContext = {
presetName: string;
};
Expand Down Expand Up @@ -105,6 +114,7 @@ export interface IAnimatedComponentInternal {
_jsPropsUpdater: IJSPropsUpdater;
_InlinePropManager: IInlinePropManager;
_PropsFilter: IPropsFilter;
_NativeEventsManager: INativeEventsManager;
_viewInfo?: ViewInfo;
context: React.ContextType<typeof SkipEnteringContext>;
}
Expand Down
100 changes: 21 additions & 79 deletions src/createAnimatedComponent/createAnimatedComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import type {
} from 'react';
import React from 'react';
import { findNodeHandle, Platform } from 'react-native';
import { WorkletEventHandler } from '../reanimated2/WorkletEventHandler';
import '../reanimated2/layoutReanimation/animationsManager';
import invariant from 'invariant';
import { adaptViewConfig } from '../ConfigHelper';
Expand All @@ -33,8 +32,9 @@ import type {
AnimatedComponentRef,
IAnimatedComponentInternal,
ViewInfo,
INativeEventsManager,
} from './commonTypes';
import { has, flattenArray } from './utils';
import { flattenArray } from './utils';
import setAndForwardRef from './setAndForwardRef';
import {
isFabric,
Expand All @@ -56,6 +56,7 @@ import type { CustomConfig } from '../reanimated2/layoutReanimation/web/config';
import type { FlatList, FlatListProps } from 'react-native';
import { addHTMLMutationObserver } from '../reanimated2/layoutReanimation/web/domUtils';
import { getViewInfo } from './getViewInfo';
import { NativeEventsManager } from './NativeEventsManager';

const IS_WEB = isWeb();

Expand Down Expand Up @@ -131,6 +132,7 @@ export function createAnimatedComponent(
_jsPropsUpdater = new JSPropsUpdater();
_InlinePropManager = new InlinePropManager();
_PropsFilter = new PropsFilter();
_NativeEventsManager: INativeEventsManager;
_viewInfo?: ViewInfo;
static displayName: string;
static contextType = SkipEnteringContext;
Expand All @@ -141,12 +143,13 @@ export function createAnimatedComponent(
if (isJest()) {
this.jestAnimatedStyle = { value: {} };
}
this._NativeEventsManager = new NativeEventsManager(this);
}

componentDidMount() {
this._setComponentViewTag();
this._setEventViewTag();
this._attachNativeEvents();
this._componentViewTag = this._getComponentViewTag();
this._eventViewTag = this._getEventViewTag();
tjzel marked this conversation as resolved.
Show resolved Hide resolved
this._NativeEventsManager.attachNativeEvents();
this._jsPropsUpdater.addOnJSPropsChangeListener(this);
this._attachAnimatedStyles();
this._InlinePropManager.attachInlineProps(this, this._getViewInfo());
Expand Down Expand Up @@ -180,7 +183,7 @@ export function createAnimatedComponent(
}

componentWillUnmount() {
this._detachNativeEvents();
this._NativeEventsManager.detachNativeEvents();
this._jsPropsUpdater.removeOnJSPropsChangeListener(this);
this._detachStyles();
this._InlinePropManager.detachInlineProps();
Expand Down Expand Up @@ -226,46 +229,24 @@ export function createAnimatedComponent(
}
}

_setComponentViewTag() {
this._componentViewTag = this._getViewInfo().viewTag as number;
_getComponentViewTag() {
return this._getViewInfo().viewTag as number;
}

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

_attachNativeEvents() {
for (const key in this.props) {
const prop = this.props[key];
if (
has('workletEventHandler', prop) &&
prop.workletEventHandler instanceof WorkletEventHandler
) {
prop.workletEventHandler.registerForEvents(this._eventViewTag, key);
}
}
}

_detachNativeEvents() {
for (const key in this.props) {
const prop = this.props[key];
if (
has('workletEventHandler', prop) &&
prop.workletEventHandler instanceof WorkletEventHandler
) {
prop.workletEventHandler.unregisterFromEvents(this._eventViewTag);
}
}
return newTag;
}

_detachStyles() {
Expand All @@ -288,48 +269,6 @@ export function createAnimatedComponent(
}
}

_updateNativeEvents(
prevProps: AnimatedComponentProps<InitialComponentProps>
) {
for (const key in prevProps) {
const prevProp = prevProps[key];
if (
has('workletEventHandler', prevProp) &&
prevProp.workletEventHandler instanceof WorkletEventHandler
) {
const newProp = this.props[key];
if (!newProp) {
// Prop got deleted
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._eventViewTag
);
newProp.workletEventHandler.registerForEvents(this._eventViewTag);
}
}
}

for (const key in this.props) {
const newProp = this.props[key];
if (
has('workletEventHandler', newProp) &&
newProp.workletEventHandler instanceof WorkletEventHandler &&
!prevProps[key]
) {
// Prop got added
newProp.workletEventHandler.registerForEvents(this._eventViewTag);
}
}
}

_updateFromNative(props: StyleProps) {
if (options?.setNativeProps) {
options.setNativeProps(this._component as AnimatedComponentRef, props);
Expand Down Expand Up @@ -477,7 +416,10 @@ export function createAnimatedComponent(
) {
this._configureSharedTransition();
}
this._updateNativeEvents(prevProps);
this._NativeEventsManager.updateNativeEvents(
prevProps,
this._getEventViewTag()
);
this._attachAnimatedStyles();
this._InlinePropManager.attachInlineProps(this, this._getViewInfo());

Expand Down