Skip to content

Custom DraggableWrapper won’t let items escape their scroll container #45

@arjar88

Description

@arjar88

In my Expo app I’m using @mgcrea/react-native-dnd to let users drag recipe cards out of a horizontal list and drop them onto day‐slots elsewhere on screen. Because the stock gets its touch events swallowed by the ScrollView, I built a custom DraggableWrapper with useDraggable. Inside the scroll, cards drag fine—but the moment I move the finger beyond the horizontal list’s bounds, the card get hidden by the compoenents outside the scrollview and cannot be dragged utside of the ScrollView bounds.

Reproduction Steps

Wrap the entire app in a single inside a GestureHandlerRootView.

Use this DraggableWrapper.tsx for each card:

// DraggableWrapper.tsx
import React, { PropsWithChildren } from "react";
import {
  Gesture,
  GestureDetector,
  State as RNGHState,
} from "react-native-gesture-handler";
import Animated, {
  useAnimatedStyle,
  withSpring,
  runOnJS,
} from "react-native-reanimated";
import { useDraggable } from "@mgcrea/react-native-dnd";
import type { ViewStyle } from "react-native";

interface BaseProps {
  id: string;
  data?: any;
  disabled?: boolean;
  style?: ViewStyle;
  onDragStart?: () => void;
  onDragEnd?: () => void;
}

export type DraggableWrapperProps = PropsWithChildren<BaseProps>;

export const DraggableWrapper: React.FC<DraggableWrapperProps> = ({
  id,
  data,
  disabled = false,
  style,
  onDragStart,
  onDragEnd,
  children,
}) => {
  const {
    offset,
    props: draggableProps,
    activeId,
    panGestureState,
  } = useDraggable({ id, data, disabled });

  const panGesture = Gesture.Pan()
    .onBegin(() => {
      panGestureState.value = RNGHState.ACTIVE;
      if (onDragStart) runOnJS(onDragStart)();
    })
    .onUpdate((e) => {
      offset.x.value = e.translationX;
      offset.y.value = e.translationY;
    })
    .onEnd(() => {
      panGestureState.value = RNGHState.END;
      offset.x.value = withSpring(0);
      offset.y.value = withSpring(0);
      if (onDragEnd) runOnJS(onDragEnd)();
    });

  const gesture = Gesture.Simultaneous(panGesture, Gesture.Native());

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [
      { translateX: offset.x.value },
      { translateY: offset.y.value },
    ],
    zIndex: activeId.value === id ? 999 : 1,
  }));

  return (
    <GestureDetector gesture={gesture}>
      <Animated.View
        {...draggableProps}
        style={[style, animatedStyle, { overflow: "visible" }]}
      >
        {children}
      </Animated.View>
    </GestureDetector>
  );
};

Render your list like this:


// In your component
import { ScrollView as GHScrollView } from "react-native-gesture-handler";
import { DraggableWrapper } from "./DraggableWrapper";

<GHScrollView
  horizontal
  removeClippedSubviews={false}
  style={{ overflow: "visible" }}
  contentContainerStyle={{ overflow: "visible" }}
>
  {recipes.map((recipe) => (
    <DraggableWrapper key={recipe.id} id={recipe.id} style={{ margin: 8 }}>
      <MealCard meal={recipe} onPress={handleMealCardPress} />
    </DraggableWrapper>
  ))}
</GHScrollView>;
Long-press and drag a card. It moves within the list, but as soon as your finger crosses the list’s edge, the card is clipped/hidden and cannot be moved further or dropped outside.

Actual Behavior

  • Cards drag only within the scroll view’s bounds.
  • Once dragged beyond those bounds they get hidden behind compoenents outside the scrollview.
  • useDraggable’s activeId remains set, but no drop events fire.

Expected Behavior

After activation, the card should lift out of the scroll container into a full-screen overlay, follow the finger freely in all directions, and onDragEnd should fire on release—regardless of leaving the original scroll bounds.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions