0.12.0 • Published 2 months ago

react-native-reorderable-list v0.12.0

Weekly downloads
-
License
MIT
Repository
github
Last release
2 months ago

NPM Downloads GitHub License NPM Version iOS Android

React Native Reorderable List

A reorderable list for React Native applications, powered by Reanimated 🚀

Demo

Index

Install

NOTE: This package follows semantic versioning. Versions 0.X.0 are safe to use but may introduce breaking changes, as the public API is not considered stable yet.

Npm:

npm install --save react-native-reorderable-list

Yarn:

yarn add react-native-reorderable-list

Then you need to install these two peer dependencies:

Components

ReorderableList

This component uses a FlatList and it extends its props:

PropsTypeRequiredDefaultDescription
autoscrollThresholdnumberNo0.1Threshold at the extremity of the list that triggers autoscroll when an item is dragged to it. A value of 0.1 means that 10% of the area at the top and 10% at the bottom will trigger autoscroll. Min value: 0. Max value: 0.4.
autoscrollThresholdOffset{top?: number; bottom?: number}No{top: 0, bottom: 0}Amount by which the threshold is offset at the extremety of the list. For example, setting {top: 50} will make the autoscroll trigger 50 pixels earlier at the top.
autoscrollSpeedScalenumberNo1Scales the autoscroll speed at which the list scrolls when an item is dragged to the scroll areas.
autoscrollDelaynumberNo0 (Android), 100 (iOS)Delay in between autoscroll triggers. Can be used to tune the autoscroll smoothness. Default values differ between platforms: 0 for Android and 100 for iOS.
autoscrollActivationDeltanumberNo5Allows configuring the delta for autoscroll activation when dragging an item in the same direction as the autoscroll. This is particularly useful when an item is dragged within the autoscroll area to account for minor unintentional movements.
dragReorderThresholdnumberNo0.2Specifies the fraction of an item's size at which it will shift when a dragged item crosses over it. For example, 0.2 means the item shifts when the dragged item passes 20% of its height (in a vertical list).
animationDurationnumberNo200Duration of the animations in milliseconds. Users won't be able to drag a new item until the dragged item is released and its animation to its new position ends.
cellAnimationsReorderableListCellAnimationsNoN/AAllows passing an object with values and/or shared values that can animate a cell, for example by using the onDragStart and onDragEnd events. Supports view style properties. Override opacity and/or transform to disable the default animation, e.g. {opacity: 1, transform: []}. Check the examples for more details.
shouldUpdateActiveItembooleanNofalseWhether the active item should be updated. Enables usage of useIsActive hook.
panEnabledbooleanNotrueWether the pan gestures necessary for dragging are enabled.
panActivateAfterLongPressnumberNoN/ADuration in milliseconds of the long press on the list before the pan gesture for dragging is allowed to activate.
onReorder(event: { from: number, to: number }) => voidYesN/AEvent fired after an item is released and the list is reordered.
onDragStart(event: { index: number }) => voidNoN/AEvent fired when an item is dragged. Needs to be a worklet. See Reanimated docs for further info.
onDragEnd(event: { from: number, to: number }) => voidNoN/AEvent fired when the dragged item is released. Needs to be a worklet. See Reanimated docs for further info.
onIndexChange(event: { index: number }) => voidNoN/AEvent fired when the index of the dragged item changes. Needs to be a worklet. See Reanimated docs for further info.
onScrollReturnType<typeof useAnimatedScrollHandler>NoN/AAn animated scroll handler created with useAnimatedScrollHandler. See Reanimated docs for further info.

The following props from FlatList are not supported:

  • horizontal
  • scrollEventThrottle
  • removeClippedSubviews
  • CellRendererComponent
  • numColumns

ScrollViewContainer

This component extends the ScrollView component and is used for nesting a NestedReorderableList within a scrollable container:

PropsTypeRequiredDefaultDescription
onScrollReturnType<typeof useAnimatedScrollHandler>NoN/AAn animated scroll handler created with useAnimatedScrollHandler. See Reanimated docs for further info.

NestedReorderableList

This component allows nesting a reorderable list within a ScrollViewContainer:

PropsTypeRequiredDefaultDescription
scrollablebooleanNofalseWhether the nested list is scrollable or not. If the nested list has a fixed height and it's scrollable it should be set to true, otherwise false.

Hooks

useReorderableDrag

This hook creates a function that triggers the drag of a list item. It's usually called on a long press event. This hook can only be used inside of a list item component.

Returns
  • () => void

useReorderableDragStart

This hook allows handling the drag start event of a list item. This hook can only be used inside of a list item component. It receives a worklet callback that is called when the drag starts. It's recommended to wrap the handler function in a useCallback as follows:

useReorderableDragStart(
  useCallback((index: number) => {
    'worklet';

    // ...
  }, []),
);

Using this hook in large lists is discouraged due to performance bottlenecks, prefer onDragStart on the ReorderableList instead.

useReorderableDragEnd

This hook allows handling the drag end event of a list item. This hook can only be used inside of a list item component. It receives a worklet callback that is called when the drag starts. It's recommended to wrap the handler function in a useCallback as follows:

useReorderableDragEnd(
  useCallback((from: number, to: number) => {
    'worklet';

    // ...
  }, []),
);

Using this hook in large lists is discouraged due to performance bottlenecks, prefer onDragEnd on the ReorderableList instead.

useIsActive

This hook returns a boolean indicating whether the current item is active. It will return true on drag start and false on drag end. This hook can only be used inside of a list item component.

const isActive = useIsActive();

Additionally this hook requires setting shouldUpdateActiveItem to true on the ReorderableList:

<ReorderableList
  // ...
  shouldUpdateActiveItem
/>

Utils

  • reorderItems: <T>(data: T[], from: number, to: number) => T[]

    This function receives an array of items, the index of the item to be moved, and the index of the new position and it returns a new array with the items reordered.

Troubleshooting

RefreshControl

If you want to use RefreshControl with ReorderableList you might encounter some issues on Android, where gestures are conflicting making one or both of the components non responsive. To overcome this issues you can delay the activation of pan gestures necessary for dragging items by using the panActivateAfterLongPress prop. This duration should be slightly longer than the long press delay necessary to drag your items. If you're using Pressable the delayLongPress is 500 ms by default.

<ReorderableList
  // ...
  panActivateAfterLongPress={Platform.OS === 'android' ? 520 : undefined}
/>

If you change delayLongPress on your Pressable, update this prop accordingly.

Another issue you'll encounter is that when you drag your items the RefreshControl might animate. To avoid this you can enable and disable it on drag start and drag end like so:

const [refreshEnabled, setRefreshEnabled] = useState(true);

const handleDragStart = useCallback(() => {
  'worklet';

  // NOTE: If it's refreshing we don't want the refresh control to disappear
  // and we can keep it enabled since it won't conflict with the drag.
  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}
      />
    }
  />
);

Example

Here is simple example of how to use this component. Examples of nested lists and much more can be found in the example directory.

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;

License

MIT


Made with create-react-native-library

1.0.0

4 months ago

0.10.0

4 months ago

0.11.0

4 months ago

0.9.0

4 months ago

0.12.0

2 months ago

0.8.0

4 months ago

0.7.1

4 months ago

0.7.0

5 months ago

0.6.1

5 months ago

0.6.0

5 months ago

0.5.1

5 months ago

0.5.0

8 months ago

0.4.0

3 years ago

0.3.0

3 years ago

0.2.0

3 years ago