npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-native-dragflow

v0.1.0

Published

Gesture-driven, auto-scrolling draggable FlatList for React Native — vertical & horizontal — built from scratch on Reanimated 4 and Gesture Handler.

Downloads

153

Readme

react-native-dragflow

npm version license

A gesture-driven, auto-scrolling draggable FlatList for React Native — works vertically and horizontally, reorders state for you, and is built entirely from scratch on Reanimated 4 and React Native Gesture Handler. No native modules of its own; everything runs on the UI thread.

Features

  • 🟰 Vertical and horizontal drag-to-reorder from a single component.
  • 🪄 Auto-scroll at the edges — drag an item toward the edge and the list scrolls, with speed that ramps up the closer you get.
  • 🔁 Automatic reordering — pass a setListData (or listen via onChange) and the new order is applied for you.
  • 🎛️ Tunable via props — the edge trigger zone and the max scroll speed are both yours to set.
  • 👻 Ghost overlay — the lifted item renders as a floating copy you can style independently via a highlight flag.
  • 🧵 Runs on the UI thread — gestures, the ghost, the auto-scroll loop, and neighbour shifting are all Reanimated worklets, so dragging stays smooth.
  • 🧩 Forwards FlatList props — anything you'd normally pass to a FlatList (minus the props the library controls) is passed straight through.

Preview

dragflow demo


Installation

npm install react-native-dragflow
# or
yarn add react-native-dragflow

Peer dependencies

This library is a thin layer over Reanimated and Gesture Handler, so you install those in your app:

npm install react-native-reanimated react-native-worklets react-native-gesture-handler

| Peer dependency | Required version | | ------------------------- | ---------------- | | react | >= 18 | | react-native | >= 0.80 | | react-native-reanimated | >= 4.0 | | react-native-worklets | >= 0.7 |

If npm install fails with ERESOLVE, your app has a pre-existing peer-dependency conflict between Reanimated and Worklets (or another package). This is common in RN projects and is not specific to this library. Install with --legacy-peer-deps:

npm install react-native-dragflow --legacy-peer-deps

| react-native-gesture-handler | >= 2.9 |

Reanimated 4 only. This library uses scheduleOnRN from react-native-worklets, which is Reanimated 4's replacement for runOnJS. Reanimated 4 supports the New Architecture only — if your app is still on Reanimated 3 / the old architecture, stay on a Reanimated-3 draggable list instead.

Setup

Two one-time steps, both standard for any Reanimated 4 + Gesture Handler app.

1. Add the Worklets babel plugin (must be the last plugin) in babel.config.js:

module.exports = {
  presets: ["module:@react-native/babel-preset"],
  plugins: [
    // ...your other plugins
    "react-native-worklets/plugin", // keep this last
  ],
};

2. Wrap your app root in GestureHandlerRootView (the drag gestures won't fire without it):

import { GestureHandlerRootView } from "react-native-gesture-handler";

export default function App() {
  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      {/* ...your app */}
    </GestureHandlerRootView>
  );
}

Then rebuild the app (npx react-native run-android / run-ios) — a JS reload isn't enough after adding the babel plugin.


Quick start (vertical)

Press and hold an item for ~300ms to pick it up, then drag to reorder. Drag toward the top or bottom edge to auto-scroll.

import React, { useState } from "react";
import { View, Text, StyleSheet } from "react-native";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { DraggableList } from "react-native-dragflow";

type Item = { id: number; label: string };

const initialData: Item[] = Array.from({ length: 20 }, (_, i) => ({
  id: i + 1,
  label: `Item ${i + 1}`,
}));

export default function App() {
  const [data, setData] = useState<Item[]>(initialData);

  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <DraggableList<Item>
        listData={data}
        setListData={setData}
        orientation="vertical"
        separatorGap={8}
        keyExtractor={(item) => String(item.id)}
        renderDataItem={({ item, highlight }) => (
          <View style={[styles.card, highlight && styles.lifted]}>
            <Text style={styles.label}>{item.label}</Text>
          </View>
        )}
      />
    </GestureHandlerRootView>
  );
}

const styles = StyleSheet.create({
  card: { padding: 16, borderRadius: 12, backgroundColor: "#f2f2f7" },
  lifted: { backgroundColor: "#fff", borderWidth: 2, borderColor: "#ff3b30" },
  label: { fontSize: 16 },
});

Horizontal list

Set orientation="horizontal" and give items a fixed width with horizontalItemSize:

<DraggableList<Item>
  listData={data}
  setListData={setData}
  orientation="horizontal"
  horizontalItemSize={120}
  separatorGap={12}
  keyExtractor={(item) => String(item.id)}
  showsHorizontalScrollIndicator={false}
  renderDataItem={({ item, highlight }) => (
    <View style={[styles.chip, highlight && styles.lifted]}>
      <Text>{item.label}</Text>
    </View>
  )}
/>

Reacting to reorders with onChange

If your data lives outside local state (Redux, Zustand, a server sync, etc.), skip setListData and use onChange — it fires with the freshly reordered array every time a drag commits:

<DraggableList<Item>
  listData={data}
  orientation="vertical"
  separatorGap={8}
  keyExtractor={(item) => String(item.id)}
  onChange={(newData) => {
    persistOrder(newData); // your store / API call
  }}
  renderDataItem={({ item }) => <Row item={item} />}
/>

Provide at least one of setListData or onChange — otherwise the new order has nowhere to go and the list snaps back to listData on release.


Props

DraggableList takes the props below, plus any other FlatList prop (those are forwarded to the underlying list). The forwarded set excludes data, renderItem, ItemSeparatorComponent, and horizontal, which the library manages internally.

| Prop | Type | Required | Default | Description | | -------------------- | ------------------------------------ | :------: | :-----: | ----------------------------------------------------------------------------------------------------- | | listData | T[] | ✅ | — | The array of items to render. | | orientation | 'vertical' \| 'horizontal' | ✅ | — | Direction of the list and the drag. | | keyExtractor | (item: T, index: number) => string | ✅ | — | Returns a stable, unique key per item. | | renderDataItem | ({ item, highlight }) => ReactNode | ✅ | — | Renders an item. highlight is true only for the floating ghost (see below). | | separatorGap | number | ✅ | — | Gap in px between items. Also rendered as the list's separator and factored into the reorder spacing. | | setListData | Dispatch<SetStateAction<T[]>> | — | — | Your useState setter. The library calls it with the reordered array. | | onChange | (newData: T[]) => void | — | — | Called with the reordered array after each drag commits. | | dragEnabled | boolean | — | true | Turn dragging on/off without unmounting the list. | | autoScrollEdge | number | — | 120 | Size in px of the edge zone that triggers auto-scroll. | | maxSpeed | number | — | 12 | Max auto-scroll speed in px per frame. | | horizontalItemSize | number | — | 100 | Fixed item width in px. Used only when orientation="horizontal". | | ...rest | FlatList props | — | — | Forwarded to the underlying Animated.FlatList. |

The highlight flag

renderDataItem is called for two things: the items sitting in the list (highlight: false) and the ghost — the floating copy that follows your finger while dragging (highlight: true). Use it to make the lifted item look picked-up:

renderDataItem={({ item, highlight }) => (
  <View style={[styles.card, highlight && styles.lifted]}>
    <Text>{item.label}</Text>
  </View>
)}

Tuning auto-scroll

autoScrollEdge and maxSpeed together control the edge behaviour:

  • autoScrollEdge — how far from the edge (in px) the auto-scroll zone reaches. Larger = scrolling kicks in sooner; smaller = you have to drag closer to the edge.
  • maxSpeed — the fastest the list will scroll (px per frame, ~60fps). The actual speed ramps from 0 up to this cap based on how deep into the edge zone the item's centre is.
<DraggableList
  autoScrollEdge={150} // wider trigger zone
  maxSpeed={20} // faster scroll at the edge
  /* ... */
/>

How it works

  • Pick up: a Pan gesture activates after a ~300ms long-press, so normal taps and scrolls aren't hijacked.
  • Ghost overlay: on pick-up the original item fades out and an absolutely-positioned copy (highlight: true) is rendered on top, following your finger.
  • Neighbour shift: as you drag, the library computes how many slots you've crossed (a "step" coefficient) and springs the other items out of the way to preview the drop position.
  • Edge auto-scroll: a frame callback continuously scrolls the underlying list while the dragged item sits in the edge zone, and the drop target keeps tracking correctly even as the content scrolls underneath.
  • Commit: on release the array is spliced into its new order and handed back through setListData / onChange, the ghost fades out, and all shared values reset.

Everything except the final state update runs as Reanimated worklets on the UI thread; the state update is marshalled back with scheduleOnRN.

Requirements & limitations

Worth knowing before you wire it in:

  • Uniform item size. The reorder math measures one item and reuses that "step" for the whole list. All items must be the same size along the scroll axis (same height for vertical, same width for horizontal). Variable-height rows aren't supported yet.
  • New Architecture only, because it depends on Reanimated 4.
  • GestureHandlerRootView is required at the app root.
  • Long-press delay is currently fixed at 300ms and isn't yet exposed as a prop.

Known issues

These are known limitations in 0.1.0 and tracked for future releases:

  • Ghost overlay drifts on long lists (Android). With ~100+ items, the floating preview can appear slightly offset from the item being dragged. The reorder logic is unaffected — only the visual position of the ghost is wrong. iOS is not affected.
  • Fast drags can release early (Android). A very quick pickup-and-drag can cause the gesture to release before the drop position is finalized, dropping the item mid-list instead of where intended. iOS is not affected. A deliberate drag works correctly.
  • Uniform item sizes only. See Requirements & limitations — variable-sized items are not yet supported.

Issues and PRs welcome — see Contributing.

Troubleshooting

| Symptom | Likely cause | | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | Nothing happens on long-press | App isn't wrapped in GestureHandlerRootView, or the react-native-worklets/plugin babel plugin is missing / not last. Rebuild after adding it. | | scheduleOnRN / worklets error on launch | react-native-worklets isn't installed, or you're on Reanimated 3. This library needs Reanimated 4. | | Items overlap or drop into the wrong slot | Item sizes aren't uniform along the scroll axis (see limitations), or separatorGap doesn't match the actual spacing between items. | | Ghost is misaligned in a horizontal list | horizontalItemSize doesn't match the rendered item width. |

Contributing

Issues and PRs welcome. To work on the library locally:

npm install
npm run typecheck   # type-check the source
npm run prepare     # build lib/ with react-native-builder-bob

License

MIT © Harsh Mer