Skip to content

Scrollable DragList library for React Native that doesn't use Flatlist, which sometimes may cause re-render issues. Designed to work on Web, iOS & Android.

Notifications You must be signed in to change notification settings

gerwld/react-native-drag-n-drop-everywhere

Repository files navigation

react-native-drag-n-drop-everywhere

A scrollable React Native drag-and-drop library that doesn't use FlatList, which can sometimes cause re-render issues. Designed to work on Web, iOS, and Android.
Built with react-native-reanimated and react-native-gesture-handler. Supports custom item rendering, grips for drag initiation, and more.

Web Preview iOS Preview Android Preview
Web Preview iOS Preview Android Preview

Live Demo

Installation

npm install react-native-drag-n-drop-everywhere

Ensure you have installed and configured the following:

  • react-native-reanimated
  • react-native-gesture-handler
  • (Optional) For haptic feedback on iOS: expo-haptics

For setup instructions, refer to their documentation:

Usage

import { Platform, Text, View } from 'react-native';
import DragList from 'react-native-drag-n-drop-everywhere';
import { runOnJS } from 'react-native-reanimated';
import * as Haptics from 'expo-haptics';

const MyDragList = () => {
  const dataIDsArray = [
    "95a6885b-64ab-468c-9334-62c4095df459",
    "b592f039-b4d6-420a-b731-0964172ed142",
    "dba923d8-6743-47ab-8923-8e45eacdb204",
  ];

  const data = {
    "95a6885b-64ab-468c-9334-62c4095df459": 
      { title: 'Entertainment', type: "CATEGORY_TYPE_EXPENSES" },
    "b592f039-b4d6-420a-b731-0964172ed142": 
      { title: 'Groceries', type: "CATEGORY_TYPE_EXPENSES" },
    "dba923d8-6743-47ab-8923-8e45eacdb204": 
      { title: 'Sport', type: "CATEGORY_TYPE_EXPENSES" },
  };

  const renderItem = ({ item }) => (
    <View style={{ paddingHorizontal: 20 }}>
      <Text>{data[item].title}</Text>
      <Text>{data[item].type}</Text>
    </View>
  );

  const renderGrip = () => (
      <Text style={{ fontSize: 30, lineHeight: 30, fontWeight: "600" }}>:::</Text>
  );

  const onUpdateCallback = (newSortedData) => {
    // Do not change dataIDsArray directly in useState. It will cause two-way binding and extra re-renders. 
    // Instead, dispatch this value
    console.log(newSortedData);
  };

  return (
    <DragList
      dataIDs={dataIDsArray}                  // Array of IDs, required
      renderItem={renderItem}                 // Required
      callbackNewDataIds={onUpdateCallback}   // Required, callback for sorted result (dataIDs)
      renderGrip={renderGrip}

      style={{ paddingTop: 100 }}
      contentContainerStyle={{ paddingHorizontal: 10 }}
      itemContainerStyle={{ borderWidth: 2 }}

      itemsGap={10}
      itemHeight={60}
      itemBorderRadius={8}
      backgroundOnHold={"#f0f0f0"}
      passVibration={() => {
        if (Platform.OS === 'ios') {
          runOnJS(Haptics.impactAsync)(
            Haptics.ImpactFeedbackStyle.Medium
          );
        }
      }}
    />
  );
};

Preventing Extra Re-renders (Optional)

If you're using global state (e.g., Redux) to store arrayIDs and update them in callbackNewDataIds, and you want to prevent extra re-renders of your DragList component, use this approach in the parent component:

import { useStore } from 'react-redux';
import { useState } from 'react';
import { useFocusEffect } from '@react-navigation/native'

const ParentComponent = () => {
  const store = useStore();
  const [items, setItems] = useState(null);

  useFocusEffect(() => { // or, useEffect if you don't want to focus it onGoback / etc
    const state = store.getState();
    const itemsIDs = state.categories.itemsIds; // your itemsIds in state
    setItems(itemsIDs.slice()); // slice to avoid direct reference
  });

  if (!items) return null;

  return (
    // Your parent component JSX
  );
};

Props

Prop Type Default Required Description
dataIDs Array [] Yes An array of unique identifiers corresponding to each list item.
renderItem Function - Yes Function to render each list item. Receives { item } as its first argument.
callbackNewDataIds Function - Yes Callback that receives changes (sorted array of dataIDs). Note: do not change the provided dataIDs array directly in state, as it will cause two-way binding and extra re-renders.
renderGrip Function null No Optional function to render a grip for dragging.
itemsGap Number 5 No The space (in pixels) between each list item.
itemHeight Number 50 No The height (in pixels) of each item.
style Object {} No Custom styles for the ScrollView.
contentContainerStyle Object {} No Custom styles for the content container of the ScrollView.
itemContainerStyle Object {} No Custom styles for the container of each individual item.
itemBorderRadius Number 10 No The border radius of each draggable item.
backgroundOnHold String #e3e3e3 No The background color of an item while it's being dragged.
passVibration Function null No Function to trigger haptic feedback when an item starts moving.

Notes

  • dataIDs: Should be an array of unique identifiers (strings) that correspond to each list item.
  • keyExtractor: A function that extracts the key from each item in the data. Commonly, this will be the item's ID.
  • renderGrip: Customize the drag handle or "grip" for each item. Defaults to a Text grip if not provided.
  • passVibration: For iOS, you can use Expo Haptics to trigger haptic feedback when the drag starts.

License

MIT


About

Scrollable DragList library for React Native that doesn't use Flatlist, which sometimes may cause re-render issues. Designed to work on Web, iOS & Android.

Resources

Stars

Watchers

Forks

Packages

No packages published