API ReferenceHooks
useGridSortable Hook
useGridSortable Hook
A hook for creating sortable grid items with drag-and-drop reordering capabilities, position animations, multi-directional auto-scrolling, and configurable reordering strategies.
Overview
The useGridSortable hook provides the core functionality for individual items within a sortable grid, handling drag gestures, position animations, auto-scrolling, and reordering logic. It works in conjunction with useGridSortableList to provide a complete sortable grid solution. This is the grid equivalent of useSortable.
Import
import { useGridSortable } from "react-native-reanimated-dnd";Parameters
UseGridSortableOptions<T>
Core Parameters
id
- Type:
string - Required: Yes
- Description: Unique identifier for this sortable grid item. Used for tracking position and reordering.
positions
- Type:
SharedValue<GridPositions> - Required: Yes
- Description: Shared value containing the position mapping for all items in the sortable grid. Each position includes index, row, column, x, and y coordinates.
scrollY
- Type:
SharedValue<number> - Required: Yes
- Description: Shared value tracking the current vertical scroll position.
scrollX
- Type:
SharedValue<number> - Required: Yes
- Description: Shared value tracking the current horizontal scroll position.
autoScrollDirection
- Type:
SharedValue<GridScrollDirection> - Required: Yes
- Description: Current auto-scroll direction state. Supports eight directions plus none.
itemsCount
- Type:
number - Required: Yes
- Description: Total number of items in the sortable grid.
dimensions
- Type:
GridDimensions - Required: Yes
- Description: Grid dimension configuration containing
columns,rows,itemWidth,itemHeight,rowGap, andcolumnGap.
orientation
- Type:
GridOrientation - Required: Yes
- Description: Grid orientation (
VerticalorHorizontal). Determines how items flow and how position calculations are performed.
const { animatedStyle, panGestureHandler } = useGridSortable({
id: "item-1",
positions: positionsSharedValue,
scrollY: scrollYSharedValue,
scrollX: scrollXSharedValue,
autoScrollDirection: scrollDirectionValue,
itemsCount: 12,
dimensions: {
columns: 3,
itemWidth: 100,
itemHeight: 100,
columnGap: 8,
rowGap: 8,
},
orientation: GridOrientation.Vertical,
});Optional Parameters
strategy
- Type:
GridStrategy - Default:
GridStrategy.Insert - Description: Reordering strategy.
Insertshifts items between source and target to fill the gap.Swapdirectly swaps the source and target items.
import { GridStrategy } from "react-native-reanimated-dnd";
const { animatedStyle, panGestureHandler } = useGridSortable({
id: "item-1",
positions,
scrollY,
scrollX,
autoScrollDirection,
itemsCount: 12,
dimensions,
orientation: GridOrientation.Vertical,
strategy: GridStrategy.Swap,
});containerWidth
- Type:
number - Default:
500 - Description: Width of the scrollable container in pixels. Used for auto-scroll calculations and determining horizontal scroll boundaries.
containerHeight
- Type:
number - Default:
500 - Description: Height of the scrollable container in pixels. Used for auto-scroll calculations and determining vertical scroll boundaries.
activationDelay
- Type:
number - Default:
200 - Required: No
- Description: Delay in milliseconds before drag activates. The hook internally calls
Gesture.Pan().activateAfterLongPress(activationDelay)to prevent accidental drags during scrolling.
onMove
- Type:
(id: string, from: number, to: number) => void - Required: No
- Description: Callback fired when the item's position changes within the grid. Called when the item is displaced by another dragging item.
const { animatedStyle, panGestureHandler } = useGridSortable({
id: "item-1",
positions,
scrollY,
scrollX,
autoScrollDirection,
itemsCount: 12,
dimensions,
orientation: GridOrientation.Vertical,
onMove: (id, from, to) => {
console.log(`Item ${id} moved from position ${from} to ${to}`);
},
});onDragStart
- Type:
(id: string, position: number) => void - Required: No
- Description: Callback fired when dragging starts for this item.
onDrop
- Type:
(id: string, position: number, allPositions?: GridPositions) => void - Required: No
- Description: Callback fired when dragging ends. Receives the final position and optionally the complete positions map for all items.
onDragging
- Type:
(id: string, overItemId: string | null, x: number, y: number) => void - Required: No
- Description: Callback fired continuously while dragging. Provides information about which item is being hovered over and current coordinates. Internally throttled at 50ms intervals for performance.
isBeingRemoved
- Type:
boolean - Default:
false - Description: When set to
true, the item plays a removal animation (fade out and scale down over 250ms).
Return Value
UseGridSortableReturn
animatedStyle
- Type:
StyleProp<ViewStyle> - Description: Animated styles to apply to the sortable grid item. Contains absolute positioning (top, left), dimensions (width, height from grid dimensions), z-index management, shadow effects during drag, and scale transform. When
isBeingRemovedis true, includes fade-out and scale-down animations.
import { GestureDetector } from "react-native-gesture-handler";
const { animatedStyle, panGestureHandler } = useGridSortable(options);
return (
<Animated.View style={[animatedStyle]}>
<GestureDetector gesture={panGestureHandler}>
<Animated.View style={{ flex: 1 }}>
<Text>Grid item content</Text>
</Animated.View>
</GestureDetector>
</Animated.View>
);panGestureHandler
- Type:
GestureType(fromreact-native-gesture-handler) - Description: Pan gesture for full-item drag interactions, created with
Gesture.Pan(). Automatically disabled when a handle is registered viaregisterHandle. Use withGestureDetector. Manages the full drag lifecycle: start, active movement, and finish with snap-back animation.
handlePanGestureHandler
- Type:
GestureType(fromreact-native-gesture-handler) - Description: Pan gesture for handle-only drag interactions. A separate gesture instance to avoid sharing a gesture object between two
GestureDetectorcomponents (which would cause a handlerTag mutation warning). Use withGestureDetectorinside a handle component.
isMoving
- Type:
boolean - Description: Whether this item is currently being dragged. Useful for conditional styling or behavior.
const { animatedStyle, panGestureHandler, isMoving } = useGridSortable(options);
return (
<Animated.View style={[animatedStyle, isMoving && styles.dragging]}>
<GestureDetector gesture={panGestureHandler}>
<Animated.View style={{ flex: 1 }}>
<Text>Item content</Text>
</Animated.View>
</GestureDetector>
</Animated.View>
);hasHandle
- Type:
boolean - Description: Whether this sortable grid item has a handle component. When true, only the handle can initiate dragging. When false, the entire item is draggable.
registerHandle
- Type:
(registered: boolean) => void - Description: Callback for handle components to register or unregister themselves. When a handle calls
registerHandle(true), the mainpanGestureHandleris disabled so only the handle area can initiate dragging. Call withfalseto unregister (e.g., on unmount).
Usage Examples
Basic Grid Sortable Item
import { useGridSortable } from "react-native-reanimated-dnd";
import { GestureDetector } from "react-native-gesture-handler";
import Animated from "react-native-reanimated";
function SortableGridCell({ item, positions, ...sortableProps }) {
const { animatedStyle, panGestureHandler, isMoving } = useGridSortable({
id: item.id,
positions,
...sortableProps,
onMove: (id, from, to) => {
console.log(`Item ${id} moved from ${from} to ${to}`);
},
onDragStart: (id, position) => {
console.log(`Started dragging item ${id} at position ${position}`);
hapticFeedback();
},
});
return (
<Animated.View style={[animatedStyle]}>
<GestureDetector gesture={panGestureHandler}>
<Animated.View style={{ flex: 1 }}>
<View
style={[
styles.gridCell,
{ backgroundColor: item.color },
isMoving && styles.dragging,
]}
>
<Text style={styles.emoji}>{item.emoji}</Text>
<Text style={styles.label}>{item.label}</Text>
</View>
</Animated.View>
</GestureDetector>
</Animated.View>
);
}
const styles = StyleSheet.create({
gridCell: {
flex: 1,
borderRadius: 16,
justifyContent: "center",
alignItems: "center",
},
dragging: {
opacity: 0.9,
},
emoji: {
fontSize: 28,
marginBottom: 4,
},
label: {
fontSize: 12,
fontWeight: "600",
color: "#fff",
},
});Grid Item with State Tracking
function TrackedGridItem({ item, positions, ...sortableProps }) {
const [dragState, setDragState] = useState("idle");
const [hoverTarget, setHoverTarget] = useState(null);
const { animatedStyle, panGestureHandler, isMoving } = useGridSortable({
id: item.id,
positions,
...sortableProps,
onDragStart: (id, position) => {
setDragState("dragging");
hapticFeedback();
analytics.track("grid_drag_start", { itemId: id, position });
},
onDrop: (id, position, allPositions) => {
setDragState("dropped");
setTimeout(() => setDragState("idle"), 300);
analytics.track("grid_drag_end", { itemId: id, position });
},
onDragging: (id, overItemId, x, y) => {
setHoverTarget(overItemId);
if (overItemId) {
highlightItem(overItemId);
}
},
onMove: (id, from, to) => {
showToast(`Item moved to position ${to + 1}`);
},
});
return (
<Animated.View style={[animatedStyle]}>
<GestureDetector gesture={panGestureHandler}>
<Animated.View style={{ flex: 1 }}>
<View style={[styles.gridCell, styles[dragState]]}>
<Text style={styles.emoji}>{item.emoji}</Text>
<Text style={styles.label}>{item.label}</Text>
{dragState === "dragging" && (
<Text style={styles.dragIndicator}>
{hoverTarget ? `Over: ${hoverTarget}` : "Dragging..."}
</Text>
)}
</View>
</Animated.View>
</GestureDetector>
</Animated.View>
);
}Custom Animated Grid Item
function AnimatedGridItem({ item, positions, ...sortableProps }) {
const scale = useSharedValue(1);
const opacity = useSharedValue(1);
const { animatedStyle, panGestureHandler, isMoving } = useGridSortable({
id: item.id,
positions,
...sortableProps,
onDragStart: () => {
scale.value = withSpring(1.1);
opacity.value = withTiming(0.9);
},
onDrop: () => {
scale.value = withSpring(1);
opacity.value = withTiming(1);
},
});
const customAnimatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
opacity: opacity.value,
}));
return (
<Animated.View style={[animatedStyle, customAnimatedStyle]}>
<GestureDetector gesture={panGestureHandler}>
<Animated.View style={{ flex: 1 }}>
<View style={[styles.gridCell, { backgroundColor: item.color }]}>
<Text style={styles.emoji}>{item.emoji}</Text>
<Text style={styles.label}>{item.label}</Text>
</View>
</Animated.View>
</GestureDetector>
</Animated.View>
);
}Performance Optimized Grid Item
const MemoizedGridItem = React.memo(
({ item, positions, ...sortableProps }) => {
const { animatedStyle, panGestureHandler, isMoving } = useGridSortable({
id: item.id,
positions,
...sortableProps,
onMove: useCallback((id, from, to) => {
reorderItems(id, from, to);
}, []),
onDragStart: useCallback((id, position) => {
hapticFeedback();
}, []),
});
return (
<Animated.View style={[animatedStyle]}>
<GestureDetector gesture={panGestureHandler}>
<Animated.View style={{ flex: 1 }}>
<GridCellContent item={item} isMoving={isMoving} />
</Animated.View>
</GestureDetector>
</Animated.View>
);
}
);
// Separate memoized content component
const GridCellContent = React.memo(({ item, isMoving }) => (
<View
style={[
styles.gridCell,
{ backgroundColor: item.color },
isMoving && styles.movingCell,
]}
>
<Text style={styles.emoji}>{item.emoji}</Text>
<Text style={styles.label}>{item.label}</Text>
</View>
));Removable Grid Item
function RemovableGridItem({ item, positions, isRemoving, ...sortableProps }) {
const { animatedStyle, panGestureHandler } = useGridSortable({
id: item.id,
positions,
...sortableProps,
isBeingRemoved: isRemoving,
});
return (
<Animated.View style={[animatedStyle]}>
<GestureDetector gesture={panGestureHandler}>
<Animated.View style={{ flex: 1 }}>
<View style={[styles.gridCell, { backgroundColor: item.color }]}>
<Text style={styles.emoji}>{item.emoji}</Text>
<Text style={styles.label}>{item.label}</Text>
</View>
</Animated.View>
</GestureDetector>
</Animated.View>
);
}TypeScript Support
The hook is fully typed with generic support:
import { GestureDetector } from "react-native-gesture-handler";
interface AppItem {
id: string;
label: string;
icon: string;
category: "utility" | "game" | "social";
}
function TypedGridItem({ item, positions, ...props }) {
const { animatedStyle, panGestureHandler, isMoving } =
useGridSortable<AppItem>({
id: item.id,
positions,
...props,
onMove: (id: string, from: number, to: number) => {
// All parameters are properly typed
console.log(`Item ${id} moved from ${from} to ${to}`);
},
onDrop: (
id: string,
position: number,
allPositions?: GridPositions
) => {
// Parameters are typed including optional allPositions
console.log(`Dropped item ${id} at position ${position}`);
},
});
return (
<Animated.View style={[animatedStyle]}>
<GestureDetector gesture={panGestureHandler}>
<Animated.View style={{ flex: 1 }}>
<Text>{item.label}</Text>
</Animated.View>
</GestureDetector>
</Animated.View>
);
}Performance Tips
- Use
React.memofor item components to prevent unnecessary re-renders - Memoize callback functions with
useCallback - Avoid complex calculations in render functions
- Use stable keys for consistent performance
- Leverage throttled
onDragging- the hook already throttles at 50ms intervals
// Good: Memoized component and callbacks
const MemoizedGridItem = React.memo(({ item, ...props }) => {
const handleMove = useCallback((id, from, to) => {
reorderItems(id, from, to);
}, []);
const { animatedStyle, panGestureHandler } = useGridSortable({
id: item.id,
...props,
onMove: handleMove,
});
return (
<Animated.View style={[animatedStyle]}>
<GestureDetector gesture={panGestureHandler}>
<Animated.View style={{ flex: 1 }}>
<ItemContent item={item} />
</Animated.View>
</GestureDetector>
</Animated.View>
);
});See Also
- SortableGridItem Component - High-level component using this hook
- useGridSortableList Hook - Grid list management hook
- SortableGrid Component - Complete sortable grid solution
- GridScrollDirection Enum - Auto-scroll direction values
- UseGridSortableOptions Type - Complete type definitions
- useSortable Hook - Vertical sortable item hook equivalent