Skip to content

Commit 8403694

Browse files
authored
Fix memory leak with mixed use of useMemo (#3763)
## Description If someone created a non-`useMemo` composedGesture with two gestures one with, and one without `useMemo`, the `config.simultanousWith` array would had held a stale reference to a gesture from a previous render, thus producing a memory leak. ## FIx We hold `GestureRefs` instead of only `handlerTags` because when initializing the array `handlerTags` are not set, we can simply collapse the array to store only `handlerTags` and remove duplicates before passing them down to native. ## Test plan Tested on the following code. ```tsx import { useMemo, useState } from "react"; import { Gesture, GestureDetector, GestureHandlerRootView, RectButton, } from "react-native-gesture-handler"; import { View } from "react-native"; const Leak = () => { const [count, setCount] = useState(0); const bigMemory = new Array(10000); const pinchGesture = useMemo(() => Gesture.Pinch(), []); const trackGesture = Gesture.Pan().onUpdate(() => { console.log("trackGesture", bigMemory.length); }); const gestures = Gesture.Simultaneous(pinchGesture, trackGesture); return ( <GestureHandlerRootView style={{ flex: 1, backgroundColor: "#f4f4f4" }}> <GestureDetector gesture={gestures}> <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}> <RectButton onPress={() => setCount(c => c + 1)} style={{ backgroundColor: "#007AFF", paddingVertical: 14, paddingHorizontal: 28, borderRadius: 10, shadowColor: "#000", shadowOpacity: 0.15, shadowOffset: { width: 0, height: 3 }, shadowRadius: 4, elevation: 4, }} > </RectButton> </View> </GestureDetector> </GestureHandlerRootView> ); }; export default Leak; ```
1 parent ff83c88 commit 8403694

1 file changed

Lines changed: 14 additions & 8 deletions

File tree

  • packages/react-native-gesture-handler/src/handlers/gestures/GestureDetector

packages/react-native-gesture-handler/src/handlers/gestures/GestureDetector/utils.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,22 +49,28 @@ function convertToHandlerTag(ref: GestureRef): number {
4949
}
5050

5151
function extractValidHandlerTags(interactionGroup: GestureRef[] | undefined) {
52-
return (
53-
interactionGroup?.map(convertToHandlerTag)?.filter((tag) => tag > 0) ?? []
52+
return Array.from(
53+
new Set(
54+
interactionGroup?.map(convertToHandlerTag)?.filter((tag) => tag > 0) ?? []
55+
)
5456
);
5557
}
5658

5759
export function extractGestureRelations(gesture: GestureType) {
58-
const requireToFail = extractValidHandlerTags(gesture.config.requireToFail);
59-
const simultaneousWith = extractValidHandlerTags(
60+
gesture.config.requireToFail = extractValidHandlerTags(
61+
gesture.config.requireToFail
62+
);
63+
gesture.config.simultaneousWith = extractValidHandlerTags(
6064
gesture.config.simultaneousWith
6165
);
62-
const blocksHandlers = extractValidHandlerTags(gesture.config.blocksHandlers);
66+
gesture.config.blocksHandlers = extractValidHandlerTags(
67+
gesture.config.blocksHandlers
68+
);
6369

6470
return {
65-
waitFor: requireToFail,
66-
simultaneousHandlers: simultaneousWith,
67-
blocksHandlers: blocksHandlers,
71+
waitFor: gesture.config.requireToFail,
72+
simultaneousHandlers: gesture.config.simultaneousWith,
73+
blocksHandlers: gesture.config.blocksHandlers,
6874
};
6975
}
7076

0 commit comments

Comments
 (0)