react-native-reorderable-list

repository·master·Indexed 43 days ago

https://github.com/omahili/react-native-reorderable-list

A reorderable list library for React Native applications powered by Reanimated, providing smooth drag-and-drop functionality for FlatLists. Version 0.18.1 supports vertical and horizontal modes, layout animations, nested lists, and custom drop indicators. It includes the ReorderableList and NestedReorderableList components, along with hooks like useReorderableDrag and useIsActive to manage drag-and-drop interactions.

Tokens
7.4K
Snippets
19
Records
41
Agent score
55%

What's inside react-native-reorderable-list

  1. Overview of React Native Reorderable List

    master

    React Native Reorderable List is a library for implementing drag-and-drop lists in React Native applications, powered by Reanimated.

    Key Features:

    • Drag & Drop FlatList support
    • Vertical and Horizontal modes
    • Layout Animations
    • Support for Nested Lists
    • Custom Animations
    • Drop Indicators
  2. Install react-native-reorderable-list

    master

    Install the package using npm or yarn. Note that versions 0.X.0 are considered unstable and may introduce breaking changes.

    Peer Dependencies: You must also install the following peer dependencies for the library to function:

    • react-native-reanimated >= 3.12.0
    • react-native-gesture-handler >= 2.12.0
    npm install --save react-native-reorderable-list
    # or
    yarn add react-native-reorderable-list
  3. Configure ReorderableList autoscroll settings

    master

    ReorderableList supports autoscrolling when an item is dragged to the edges of the list. You can tune the behavior using the following props:

    • autoscrollThreshold: A value between 0 and 0.4 representing the percentage of the list area at the top/bottom that triggers autoscroll. Default is 0.1 (10%).
    • autoscrollThresholdOffset: An object {start?: number; end?: number} to offset the trigger point in pixels. For example, {start: 50} triggers autoscroll 50 pixels earlier at the top.
    • autoscrollSpeedScale: A multiplier for the autoscroll speed. Default is 1.
    • autoscrollDelay: Delay between autoscroll triggers. Default is 0 on Android and 100 on iOS.
    • autoscrollActivationDelta: The delta required to activate autoscroll when dragging in the same direction as the scroll. Default is 5.
  4. Use RefreshControl with ReorderableList on Android

    master

    On Android, RefreshControl and ReorderableList gestures can conflict. To fix this:

    1. Delay Pan Activation: Use activateAfterLongPress on the pan gesture (set it slightly higher than your item's long-press delay).
    2. Toggle RefreshControl: Manually enable/disable the RefreshControl during drag events using onDragStart and onDragEnd to prevent the control from animating while dragging.
    const [refreshEnabled, setRefreshEnabled] = useState(true);
    
    const handleDragStart = useCallback(() => {
      'worklet';
      if (Platform.OS === 'android' && !refreshing) {
        runOnJS(setRefreshEnabled)(false);
      }
    }, [refreshing]);
    
    const handleDragEnd = useCallback(() => {
      'worklet';
      if (Platform.OS === 'android') {
        runOnJS(setRefreshEnabled)(true);
      }
    }, []);
    
    return (
      <ReorderableList
        // ...
        onDragStart={handleDragStart}
        onDragEnd={handleDragEnd}
        refreshControl={
          <RefreshControl
            // ...
            enabled={refreshEnabled}
          />
        }
      />
    );
  5. Resolve navigation gesture conflicts

    master

    If you have gesture-based navigation (like swipe-to-go-back) that conflicts with the reorderable list, you can use one of the following strategies with react-native-gesture-handler:

    1. Delay activation: Set activateAfterLongPress on the pan gesture to a value slightly longer than your list's long-press delay (e.g., if using Pressable with default 500ms, use 520ms).
    2. Increase activation offset: Set a larger activeOffsetX to require more horizontal movement before the drag starts.
    3. Use negative hitSlop: This can prevent accidental drags from the edges, but note it disables drag starts from the sides of items.
    import {Gesture} from 'react-native-gesture-handler';
    
    // Strategy 1: Delay activation
    const panGesture = useMemo(() => Gesture.Pan().activateAfterLongPress(520), []);
    
    // Strategy 2: Activation offset
    const panGesture = useMemo(
      () => Gesture.Pan().activeOffsetX([-20, 20]).activeOffsetY([0, 0]),
      [],
    );
    
    // Strategy 3: Negative hitSlop
    const panGesture = useMemo(() => Gesture.Pan().hitSlop(-10), []);
    
    return (
      <ReorderableList
        // ...
        panGesture={panGesture}
      />
    );
  6. Complete ReorderableList usage example

    master

    This example demonstrates a basic implementation of ReorderableList using Pressable for long-press drag activation and reorderItems for state updates.

    Important: Always use a stable and unique keyExtractor (like an id). Do not use the array index as a key.

    import React, {memo, useState} from 'react';
    import {ListRenderItemInfo, Pressable, StyleSheet, Text} from 'react-native';
    
    import ReorderableList, {
      ReorderableListReorderEvent,
      reorderItems,
      useReorderableDrag,
    } from 'react-native-reorderable-list';
    
    interface CardProps {
      id: string;
      color: string;
      height: number;
    }
    
    const rand = () => Math.floor(Math.random() * 256);
    
    const seedData: CardProps[] = Array(20)
      .fill(null)
      .map((_, i) => ({
        id: i.toString(),
        color: `rgb(${rand()}, ${rand()}, ${rand()})`,
        height: Math.max(60, Math.floor(Math.random() * 100)),
      }));
    
    const Card: React.FC<CardProps> = memo(({id, color, height}) => {
      const drag = useReorderableDrag();
    
      return (
        <Pressable style={[styles.card, {height}]} onLongPress={drag}>
          <Text style={[styles.text, {color}]}>Card {id}</Text>
        </Pressable>
      );
    });
    
    const Example = () => {
      const [data, setData] = useState(seedData);
    
      const handleReorder = ({from, to}: ReorderableListReorderEvent) => {
        setData(value => reorderItems(value, from, to));
      };
    
      const renderItem = ({item}: ListRenderItemInfo<CardProps>) => (
        <Card {...item} />
      );
    
      return (
        <ReorderableList
          data={data}
          onReorder={handleReorder}
          renderItem={renderItem}
          keyExtractor={item => item.id}
        />
      );
    };
    
    const styles = StyleSheet.create({
      card: {
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: 'white',
        borderBottomWidth: 1,
        borderBottomColor: '#ddd',
      },
      text: {
        fontSize: 20,
      },
    });
    
    export default Example;
  7. Check if an item is active with useIsActive

    master

    The useIsActive hook returns a boolean indicating whether the current item is being dragged. It returns true on drag start and false on drag end.

    Requirement: To use this hook, you must set the shouldUpdateActiveItem prop to true on the ReorderableList component.

    Note: This hook must be used inside a list item component.

    // Inside a list item component
    const isActive = useIsActive();
    
    // On the ReorderableList
    <ReorderableList
      // ...
      shouldUpdateActiveItem
    />
  8. Customize ReorderableList animations and indicators

    master

    You can enhance the visual feedback of the list using these props:

    • renderDropIndicator: A function of type ReorderableListRenderItem<T> that renders a custom indicator showing where an item will be dropped.
    • cellAnimations: An object containing values or shared values to animate cell properties (like opacity or transform) during onDragStart and onDragEnd. To disable default animations, use {opacity: 1, transform: []}.
    • itemLayoutAnimation: A layout animation applied when items enter or leave the view hierarchy.
  9. Use ScrollViewContainer for nested lists

    master

    If you need to nest a NestedReorderableList inside a scrollable container, use the ScrollViewContainer component. This component extends ScrollView and is designed to work with Reanimated.

    Props:

    • onScroll: Must be an animated scroll handler created with useAnimatedScrollHandler from Reanimated.
  10. Use the ReorderableList component

    master

    The ReorderableList component is the primary component for creating reorderable lists. It extends FlatList props and provides built-in support for drag-and-drop reordering, autoscrolling, and animations.

    Key Requirements:

    • You must provide an onReorder callback to handle the list reordering logic after an item is released.
    • Several event callbacks (onDragStart, onDragEnd, onIndexChange) must be defined as Reanimated worklets.

    Unsupported FlatList Props:

    • scrollEventThrottle
    • removeClippedSubviews
    • CellRendererComponent
    • numColumns
    <ReorderableList
      data={data}
      onReorder={(event) => handleReorder(event)}
      renderItem={({ item }) => <YourItem item={item} />}
    />
  11. Handle drag start and end events with hooks

    master

    You can use useReorderableDragStart and useReorderableDragEnd inside a list item component to handle lifecycle events. Both hooks receive a worklet callback. It is recommended to wrap these handlers in useCallback.

    Performance Warning: Using these hooks in large lists is discouraged due to performance bottlenecks. For large lists, prefer using the onDragStart and onDragEnd props on the ReorderableList component instead.

    useReorderableDragStart

    Receives a callback with the current index.

    useReorderableDragEnd

    Receives a callback with from and to indices.

    // Example: useReorderableDragStart
    useReorderableDragStart(
      useCallback((index: number) => {
        'worklet';
        // ...
      }, []),
    );
    
    // Example: useReorderableDragEnd
    useReorderableDragEnd(
      useCallback((from: number, to: number) => {
        'worklet';
        // ...
      }, []),
    );
    useReorderableDragStart(
      useCallback((index: number) => {
        'worklet';
    
        // ...
      }, []),
    );
    
    useReorderableDragEnd(
      useCallback((from: number, to: number) => {
        'worklet';
    
        // ...
      }, []),
    );
  12. Use NestedReorderableList within a ScrollViewContainer

    master

    NestedReorderableList allows you to place a reorderable list inside a ScrollViewContainer to handle complex nested scrolling scenarios.

    Props:

    • scrollable: A boolean indicating if the nested list is scrollable. Set to true if the nested list has a fixed height and its own scrollable area; otherwise, set to false.