Skip to content

Commit a6741a9

Browse files
authoredOct 8, 2024
[Web] Remove findNodeHandle function. (#3127)
## Description `findNodeHandle` is a utility function that allows us to get native view from React component. The problem is, that under the hood it uses `findDOMNode`, which is deprecated and [will be removed in React 19](https://react.dev/reference/react-dom/findDOMNode). To get rid of this function, I've written our implementation that meets our requirements. Also, for new API we had to introduce a little trick (thanks @j-piasecki) - we add wrapper `div` with `display: contents;` style and we pass a ref to this element. With that change, it is much easier to get required `HTMLElement` - we simply have to get `div` from `ref` and find first child (preferably the one without `display: contents;`) >[!WARNING] > You may find tat in `StrictMode` most of new API examples doesn't work. That's not really the case. If you check example from [this comment](#3127 (comment)) it works fine. What really don't work in these examples are the animations. ## Test plan Tested that example app works and no `findNodeHandle` error is thrown in `StrictMode`
1 parent 671b66f commit a6741a9

8 files changed

+84
-5
lines changed
 

‎src/findNodeHandle.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { findNodeHandle } from 'react-native';
2+
3+
export default findNodeHandle;

‎src/findNodeHandle.web.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
type GestureHandlerRef = {
2+
viewTag: GestureHandlerRef;
3+
current: HTMLElement;
4+
};
5+
6+
export default function findNodeHandle(
7+
viewRef: GestureHandlerRef | HTMLElement
8+
): HTMLElement | number {
9+
// Old API assumes that child handler is HTMLElement.
10+
// However, if we nest handlers, we will get ref to another handler.
11+
// In that case, we want to recursively call findNodeHandle with new handler viewTag (which can also be ref to another handler).
12+
if ((viewRef as GestureHandlerRef)?.viewTag !== undefined) {
13+
return findNodeHandle((viewRef as GestureHandlerRef).viewTag);
14+
}
15+
16+
if (viewRef instanceof HTMLElement) {
17+
if (viewRef.style.display === 'contents') {
18+
return findNodeHandle(viewRef.firstChild as HTMLElement);
19+
}
20+
21+
return viewRef;
22+
}
23+
24+
// In new API, we receive ref object which `current` field points to wrapper `div` with `display: contents;`.
25+
// We want to return the first descendant (in DFS order) that doesn't have this property.
26+
let element = viewRef?.current;
27+
28+
while (element && element.style.display === 'contents') {
29+
element = element.firstChild as HTMLElement;
30+
}
31+
32+
return element;
33+
}

‎src/handlers/createHandler.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import {
1616
GestureEvent,
1717
HandlerStateChangeEvent,
1818
} from './gestureHandlerCommon';
19-
import { filterConfig, findNodeHandle, scheduleFlushOperations } from './utils';
19+
import { filterConfig, scheduleFlushOperations } from './utils';
20+
import findNodeHandle from '../findNodeHandle';
2021
import { ValueOf } from '../typeUtils';
2122
import { deepEqual, isFabric, isJestEnv, tagMessage } from '../utils';
2223
import { ActionType } from '../ActionType';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import React, { forwardRef } from 'react';
2+
import type { LegacyRef, PropsWithChildren } from 'react';
3+
import { tagMessage } from '../../../utils';
4+
5+
export const Wrap = forwardRef<HTMLDivElement, PropsWithChildren<{}>>(
6+
({ children }, ref) => {
7+
try {
8+
// I don't think that fighting with types over such a simple function is worth it
9+
// The only thing it does is add 'collapsable: false' to the child component
10+
// to make sure it is in the native view hierarchy so the detector can find
11+
// correct viewTag to attach to.
12+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
13+
const child: any = React.Children.only(children);
14+
15+
const clone = React.cloneElement(
16+
child,
17+
{ collapsable: false },
18+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
19+
child.props.children
20+
);
21+
22+
return (
23+
<div
24+
ref={ref as LegacyRef<HTMLDivElement>}
25+
style={{ display: 'contents' }}>
26+
{clone}
27+
</div>
28+
);
29+
} catch (e) {
30+
throw new Error(
31+
tagMessage(
32+
`GestureDetector got more than one view as a child. If you want the gesture to work on multiple views, wrap them with a common parent and attach the gesture to that view.`
33+
)
34+
);
35+
}
36+
}
37+
);
38+
39+
// On web we never take a path with Reanimated,
40+
// therefore we can simply export Wrap
41+
export const AnimatedWrap = Wrap;

‎src/handlers/gestures/GestureDetector/index.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import React, {
66
useMemo,
77
useRef,
88
} from 'react';
9-
import { Platform, findNodeHandle } from 'react-native';
9+
import { Platform } from 'react-native';
10+
import findNodeHandle from '../../../findNodeHandle';
1011
import { GestureType } from '../gesture';
1112
import { UserSelect, TouchAction } from '../../gestureHandlerCommon';
1213
import { ComposedGesture } from '../gestureComposition';

‎src/handlers/gestures/GestureDetector/useDetectorUpdater.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import React, { useCallback } from 'react';
2-
import { findNodeHandle } from 'react-native';
32
import { GestureType } from '../gesture';
43
import { ComposedGesture } from '../gestureComposition';
54

@@ -13,6 +12,7 @@ import { updateHandlers } from './updateHandlers';
1312
import { needsToReattach } from './needsToReattach';
1413
import { dropHandlers } from './dropHandlers';
1514
import { useForceRender, validateDetectorChildren } from './utils';
15+
import findNodeHandle from '../../../findNodeHandle';
1616

1717
// Returns a function that's responsible for updating the attached gestures
1818
// If the view has changed, it will reattach the handlers to the new view

‎src/handlers/gestures/GestureDetector/useViewRefHandler.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { getShadowNodeFromRef } from '../../../getShadowNodeFromRef';
33

44
import { GestureDetectorState } from './types';
55
import React, { useCallback } from 'react';
6-
import { findNodeHandle } from 'react-native';
6+
import findNodeHandle from '../../../findNodeHandle';
77

88
declare const global: {
99
isFormsStackingContext: (node: unknown) => boolean | null; // JSI function

‎src/web/tools/GestureHandlerWebDelegate.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { findNodeHandle } from 'react-native';
1+
import findNodeHandle from '../../findNodeHandle';
22
import type IGestureHandler from '../handlers/IGestureHandler';
33
import {
44
GestureHandlerDelegate,

0 commit comments

Comments
 (0)