markmccoid / react-native-drag-to-reorder

React Native Drag and Drop ScrollView implementation using Reanimated 2, React Native Gesture Handler and Moti.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

React Native Drag to ReOrder

This code can be used to take a list of items and using a ScrollView, display them allowing the user to reorder the list of items via Drag and Drop.

I am not sure how to make this into an NPM Module and don't have time currently to research this, but if the following dependancies are installed in a project, you can simply drop the ./components/DragAndSort/ folder into your project. I export all the needed types and components from this directory.

Dependancies

  • react-native
  • reanimated v2
  • react-native-gesture-handler
  • moti
  • @expo/vector-icons

This Repo

If you clone this whole repository you will get the whole sample project to work with. However, the only folder you need in your project will be the ./components/DragAndSort/ folder.

I hope this can help others implement this type of functionality in their React Native applications. If, in using this code, you have any questions OR find a better/more clear way of implementing functionality, please let me know! I love to learn!

Usage

The usage is to simply pass you items to be sorted and children to the DragDropEntry component. The children you will be passing should be components that:

  1. Have an id prop
  2. Have a height that MATCHES the itemHeight props value.

NOTE: A prop called isMoving will be injected into each item. You can use this prop to change behavior when item is moving versus not.

Here is an example of using the component.

The DragDropEntry component is the parent component that wraps the children Items that you want to be able to drag and drop.

There is a helper function, sortArray, that is very useful in reordering and resetting any position/index field in your list.

import DragDropEntry, { sortArray, TScrollFunctions } from "../components/DragDrop";

const items = itemList: [
    { id: "a", name: "Coconut Milk", pos: 0 },
    { id: "b", name: "Lettuce", pos: 1 },
    { id: "c", name: "Walnuts", pos: 2 },
    { id: "d", name: "Chips", pos: 3 },
    { id: "e", name: "Ice Cream", pos: 4 },
    { id: "f", name: "Carrots", pos: 5 },
    { id: "g", name: "Onions", pos: 6 },
    { id: "h", name: "Cheese", pos: 7 },
    { id: "i", name: "Frozen Dinners", pos: 8 },
    { id: "j", name: "Yogurt", pos: 9 },
    { id: "k", name: "Kombucha", pos: 10 },
    { id: "l", name: "Lemons", pos: 11 },
    { id: "m", name: "Bread", pos: 12 },
  ];
... 

<DragDropEntry
	scrollStyles={{ width: "100%", height: "30%", borderWidth: 1, borderColor: "#aaa" }}
	updatePositions={(positions) =>
				updateItemList(sortArray<ItemType>(positions, items, "pos"))
		}
	getScrollFunctions={(functionObj) => setScrollFunctions(functionObj)}
	itemHeight={50}
	handlePosition="left"
	handle={AltHandle} // This is optional.  leave out if you want the default handle
	enableDragIndicator={true}
>
  {items.map((item, idx) => {
      return (
        <Item
          key={item.id}
          name={item.name}
          id={item.id}
          onRemoveItem={() => removeItemById(item.id)}
          firstItem={idx === 0 ? true : false}
        />
      );
		})}
</DragDropEntry>

DragDropEntry Props

It is helpful to see what parts make up the DragDropEntry component. Here is a visual.

2021-05-31_23-55-04

  • itemHeight - Required - The height of the items that are returned as children of this component. Needed so that we can calculate where each item should be positions.

  • updatePositions - Required - function that will run after drop that will reorder/update positions. It will be passed the positions array of objects, which you can use to reorder your array OR. you can use the sortArray helper function.

    positions = {
      id1: 3,
      id2: 1,
      id3: 2,
      id4: 0
    }
  • handle - Optional - React component to be used for handle. A default is provided. If you provide your own Handle component, take a look at Handle.tsx for an example. 2021-06-01_00-04-33

  • handlePosition - Optional - default is 'left' - either 'left' or 'right'. Positions the handle component on the left or right of each Item component.

  • enableDragIndicator - Optional - default is 'false' - Boolean that turns the drag indicator on or off. The position of the indicator is decided by the handle position. It will be opposite what the handlePosition is.

  • dragIndicator - Optional - React component to be used for drag indicator. The component used will be passed the following props:

    <DragIndicator
      itemHeight={itemHeight}
      fromLeftOrRight="left" // Or right depending on the handlePosition. This is opposite of the handle position
      currentPosition={movingPos}
      totalItems={numberOfItems}
      config={dragIndicatorConfig}
    />
  • dragIndicatorConfig - Optional- common config options passed to the dragIndicator. By changing the config items, you may not need to create a custom dragIndicator

    • translateXDistance - How far should the drag indicator travel into the item 2021-06-12_23-48-07
    • indicatorBorderWidth
    • indicatorBorderColor
    • indicatorBackgroundColor
    • indicatorBorderRadius
  • enableHapticFeedback - Optional - default is 'false' - boolean - Enables haptic feedback when moving an item.

  • scrollStyles - Optional - styles that will be spread on ScrollView styles prop.

  • getScrollFunctions - Optional - function that passes scroll function so calling component can scroll list to start or end.

    • Implementation:

    • const [scrollFunctions, setScrollFunctions] = React.useState<ScrollFunctions>();
      ...
      <DragDropEntry
          getScrollFunctions={(functionObj) => {
            setScrollFunctions(functionObj);
          }}
          ...
      > ... </DragDropEntry>
    • Now, the scrollFunctions variable can call the following:

    • scrollFunctions.scrollToEnd() or scrollFunctions.scrollToStart() or scrollFunctions.scrollToY(yPos)

sortArray helper function usage

The most common use case for Drag to Reorder will involve an array of items that you want to be able to reorder. To be practical, you will want to update the new sorted array somewhere to persist the state of that array.

This is what the updatePositions property is for. It accepts a function, which will be called every time your list of items order is changed. A positions object will be passed as the only argument to the function. You can use this object to sort your own array, or use the sortArray helper function.

First, the shape of the positions object:

export type Positions = {
  [key: string]: number;
};

// the key is the id in your original array, the value is the current position
// in the scrollview.
// 
const positionExample = {
  a: 0,
  c: 3,
  b: 1,
  d: 2
}

sortArray accepts the positions object, your item array and optionally the name of your position field (if you use one).

Here is an example usage:

<DragDropEntry
	scrollStyles={{ width: "100%", height: "30%", borderWidth: 1, borderColor: "#aaa" }}
	updatePositions={(positions) =>
				updateItemList(sortArray<ItemType>(positions, items, "pos"))
		}
  ...
>
	...
</DragDropEntry>  

NOTE: The sort array will always return the passed items array sorted and if your objects in the array include a position property. This is optional and you do NOT need a position key in your object in the array.

About

React Native Drag and Drop ScrollView implementation using Reanimated 2, React Native Gesture Handler and Moti.

License:MIT License


Languages

Language:TypeScript 98.7%Language:JavaScript 1.3%