Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -65,20 +65,16 @@ function extractValidHandlerTags(interactionGroup: GestureRef[] | undefined) {
}

export function extractGestureRelations(gesture: GestureType) {
gesture.config.requireToFail = extractValidHandlerTags(
gesture.config.requireToFail
);
gesture.config.simultaneousWith = extractValidHandlerTags(
const requireToFail = extractValidHandlerTags(gesture.config.requireToFail);
const simultaneousWith = extractValidHandlerTags(
gesture.config.simultaneousWith
);
gesture.config.blocksHandlers = extractValidHandlerTags(
gesture.config.blocksHandlers
);
const blocksHandlers = extractValidHandlerTags(gesture.config.blocksHandlers);

return {
waitFor: gesture.config.requireToFail,
simultaneousHandlers: gesture.config.simultaneousWith,
blocksHandlers: gesture.config.blocksHandlers,
waitFor: requireToFail,
simultaneousHandlers: simultaneousWith,
blocksHandlers: blocksHandlers,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,17 @@ export abstract class BaseGesture<
public handlerTag = -1;
public handlerName = '';
public config: BaseGestureConfig = {};
// Snapshot of the relations defined directly on this gesture (e.g. via
// `simultaneousWithExternalGesture`), captured before any composition extends
// them. Composition rebuilds the relation config from this snapshot on every
// `prepare`, so repeated renders don't accumulate references to gestures from
// previous renders (memory leak, see #3763), while keeping the original
// references so relations stay re-resolvable after a remount, such as a
// `react-freeze` unfreeze (see #4238).
public relationsSnapshot?: {
simultaneousWith: GestureRef[] | undefined;
requireToFail: GestureRef[] | undefined;
};
public handlers: HandlerCallbacks<EventPayloadT> = {
gestureId: -1,
handlerTag: -1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,29 @@ export class ComposedGesture extends Gesture {
requireGesturesToFail: GestureType[]
) {
if (gesture instanceof BaseGesture) {
// Capture the relations defined directly on the gesture before composition
// extends them, then always rebuild from that snapshot. Otherwise, when the
// gesture is stable (e.g. wrapped in `useMemo`) but the composition is
// recreated on every render, the relations would keep accumulating
// references to gestures from previous renders, leaking memory (see #3763).
// We keep the original references (instead of collapsing them to handler
// tags) so relations can still be re-resolved after a remount, such as a
// `react-freeze` unfreeze (see #4238).
gesture.relationsSnapshot ??= {
simultaneousWith: gesture.config.simultaneousWith,
requireToFail: gesture.config.requireToFail,
};

const newConfig = { ...gesture.config };

// No need to extend `blocksHandlers` here, because it's not changed in composition.
// The same effect is achieved by reversing the order of 2 gestures in `Exclusive`
newConfig.simultaneousWith = extendRelation(
newConfig.simultaneousWith,
gesture.relationsSnapshot.simultaneousWith,
simultaneousGestures
);
newConfig.requireToFail = extendRelation(
newConfig.requireToFail,
gesture.relationsSnapshot.requireToFail,
requireGesturesToFail
);

Expand Down
Loading