Skip to content
Merged
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 @@ -90,7 +90,7 @@ class RNGestureHandlerDetectorView(context: Context) : ReactViewGroup(context) {
for (tag in newHandlers) {
handlersToDetach.remove(tag)
if (!attachedHandlers.contains(tag)) {
if (shouldAttachGestureToChildView(tag)) {
if (shouldAttachGestureToChildView(tag) && actionType == GestureHandler.ACTION_TYPE_NATIVE_DETECTOR) {
// It might happen that `attachHandlers` will be called before children are added into view hierarchy. In that case we cannot
// attach `NativeViewGestureHandlers` here and we have to do it in `addView` method.
nativeHandlers.add(tag)
Expand Down Expand Up @@ -121,27 +121,32 @@ class RNGestureHandlerDetectorView(context: Context) : ReactViewGroup(context) {
private fun attachVirtualChildren(virtualChildrenToAttach: List<VirtualChildren>) {
val virtualChildrenToDetach = attachedVirtualHandlers.keys.toMutableSet()

for (child in virtualChildrenToAttach) {
virtualChildrenToDetach.remove(child.viewTag)
}

val registry = RNGestureHandlerModule.registries[moduleId]
?: throw Exception("Tried to access a non-existent registry")

for (child in virtualChildrenToDetach) {
for (tag in attachedVirtualHandlers[child]!!) {
registry.detachHandler(tag)
}
attachedVirtualHandlers.remove(tag)
}

for (child in virtualChildrenToAttach) {
if (!attachedVirtualHandlers.containsKey(child.viewTag)) {
attachedVirtualHandlers[child.viewTag] = mutableSetOf()
}

virtualChildrenToDetach.remove(child.viewTag)
attachHandlers(
child.handlerTags,
child.viewTag,
GestureHandler.ACTION_TYPE_VIRTUAL_DETECTOR,
attachedVirtualHandlers[child.viewTag]!!,
)
}

val registry = RNGestureHandlerModule.registries[moduleId]
?: throw Exception("Tried to access a non-existent registry")

for (tag in virtualChildrenToDetach) {
registry.detachHandler(tag)
attachedVirtualHandlers.remove(tag)
}
}

private fun tryAttachNativeHandlersToChildView(childId: Int) {
Expand Down
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious, why did the order of attach/detach change?

Copy link
Contributor Author

@akwasniewski akwasniewski Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you follow the logic in updateLogicDetector closely you find out that if a specific handler was previously attached to another view, which was then detached (like in the example) - due to how handlers are stored - they would first be attached to the new view and then detached completely while looping through logicChildrenToDetach. I thought that it also happens in attachHandlers, but after careful testing it is not the case. I also fixed it on android today. For some reason I can't replicate it on web, I have to dig into that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what is updateLogicDetector.

Copy link
Contributor Author

@akwasniewski akwasniewski Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant updateLogicChildren, sorry

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could not replicate the same issue on web due to how views are reused there, but I decided to add a similar fix to be safe.

Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ - (void)attachHandlers:(const std::vector<int> &)handlerTags
for (const int tag : handlerTags) {
[handlersToDetach removeObject:@(tag)];
if (![attachedHandlers containsObject:@(tag)]) {
if ([self shouldAttachGestureToSubview:@(tag)]) {
if ([self shouldAttachGestureToSubview:@(tag)] && actionType == RNGestureHandlerActionTypeNativeDetector) {
// It might happen that `attachHandlers` will be called before children are added into view hierarchy. In that
// case we cannot attach `NativeViewGestureHandlers` here and we have to do it in `didAddSubview` method.
[_nativeHandlers addObject:@(tag)];
Expand Down Expand Up @@ -236,22 +236,24 @@ - (void)updateVirtualChildren:(const std::vector<RNGestureHandlerDetectorVirtual
}

for (const auto &child : virtualChildren) {
if (_attachedVirtualHandlers.find(child.viewTag) == _attachedVirtualHandlers.end()) {
_attachedVirtualHandlers[child.viewTag] = [NSMutableSet set];
}

[virtualChildrenToDetach removeObject:@(child.viewTag)];

[self attachHandlers:child.handlerTags
actionType:RNGestureHandlerActionTypeVirtualDetector
viewTag:child.viewTag
attachedHandlers:_attachedVirtualHandlers[child.viewTag]];
}

for (const NSNumber *tag : virtualChildrenToDetach) {
for (id handlerTag : _attachedVirtualHandlers[tag.intValue]) {
[handlerManager.registry detachHandlerWithTag:handlerTag];
}
_attachedVirtualHandlers.erase(tag.intValue);
}

for (const auto &child : virtualChildren) {
if (_attachedVirtualHandlers.find(child.viewTag) == _attachedVirtualHandlers.end()) {
_attachedVirtualHandlers[child.viewTag] = [NSMutableSet set];
}
[self attachHandlers:child.handlerTags
actionType:RNGestureHandlerActionTypeVirtualDetector
viewTag:child.viewTag
attachedHandlers:_attachedVirtualHandlers[child.viewTag]];
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ const HostGestureDetector = (props: GestureHandlerDetectorProps) => {
if (
RNGestureHandlerModule.getGestureHandlerNode(
tag
).shouldAttachGestureToChildView()
).shouldAttachGestureToChildView() &&
actionType === ActionType.NATIVE_DETECTOR
) {
RNGestureHandlerModule.attachGestureHandler(
tag,
Expand Down Expand Up @@ -109,6 +110,14 @@ const HostGestureDetector = (props: GestureHandlerDetectorProps) => {
attachedVirtualHandlers.current.keys()
);

props.virtualChildren?.forEach((child) => {
virtualChildrenToDetach.delete(child.viewTag);
});

virtualChildrenToDetach.forEach((tag) => {
detachHandlers(EMPTY_HANDLERS, attachedVirtualHandlers.current.get(tag)!);
});

props.virtualChildren?.forEach((child) => {
if (child.viewRef.current == null) {
// We must check whether viewRef is not null as otherwise we get an error when intercepting gesture detector
Expand All @@ -118,7 +127,6 @@ const HostGestureDetector = (props: GestureHandlerDetectorProps) => {
if (!attachedVirtualHandlers.current.has(child.viewTag)) {
attachedVirtualHandlers.current.set(child.viewTag, new Set());
}
virtualChildrenToDetach.delete(child.viewTag);

const currentHandlerTags = new Set(child.handlerTags);
detachHandlers(
Expand All @@ -134,10 +142,6 @@ const HostGestureDetector = (props: GestureHandlerDetectorProps) => {
ActionType.VIRTUAL_DETECTOR
);
});

virtualChildrenToDetach.forEach((tag) => {
detachHandlers(EMPTY_HANDLERS, attachedVirtualHandlers.current.get(tag)!);
});
}, [props.virtualChildren]);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,34 @@ export function VirtualDetector<THandlerData, TConfig>(

const viewRef = useRef(null);
const [viewTag, setViewTag] = useState<number>(-1);

const virtualMethods = useRef(props.gesture.detectorCallbacks);
const handleRef = useCallback((node: any) => {
viewRef.current = node;
if (!node) {
return;
}

if (Platform.OS === 'web') {
setViewTag(node);
} else {
const tag = findNodeHandle(node);
const handleRef = useCallback(
(node: any) => {
viewRef.current = node;
if (!node) {
return;
}

const tag = Platform.OS === 'web' ? node : findNodeHandle(node);

if (tag != null) {
setViewTag(tag);
}
}
}, []);

return () => {
if (tag != null) {
const handlerTags = isComposedGesture(props.gesture)
? props.gesture.tags
: [props.gesture.tag];

unregister(tag, handlerTags);
}
};
},
[props.children]
);

useEffect(() => {
virtualMethods.current = props.gesture.detectorCallbacks;
Expand Down
Loading