Higher-order components to turn any list into animated, accessible and touch-friendly sortable lists
—
Utility functions for reordering arrays after sort operations. Note that arrayMove is deprecated in react-sortable-hoc v2.0.0 and users should install the separate array-move package instead.
Reorders array elements by moving an item from one index to another.
/**
* Utility function to reorder array items (DEPRECATED)
* @param array - Array to reorder
* @param from - Source index
* @param to - Target index
* @returns New array with moved item
* @deprecated Use the 'array-move' package instead
*/
function arrayMove<T>(
array: T[],
from: number,
to: number
): T[];⚠️ Deprecation Warning: This function will be removed in the next major release. Install the array-move package separately:
npm install array-moveUsage Examples:
import { arrayMove } from 'react-sortable-hoc'; // Deprecated
// OR (recommended)
import { arrayMove } from 'array-move';
// Basic array reordering
const items = ['A', 'B', 'C', 'D', 'E'];
const reordered = arrayMove(items, 1, 3); // Move 'B' to position 3
// Result: ['A', 'C', 'D', 'B', 'E']
// Move to beginning
const toStart = arrayMove(items, 3, 0); // Move 'D' to start
// Result: ['D', 'A', 'B', 'C', 'E']
// Move to end
const toEnd = arrayMove(items, 0, -1); // Move 'A' to end
// Result: ['B', 'C', 'D', 'E', 'A']The primary use case is handling the onSortEnd callback:
import React, { useState } from 'react';
import { SortableContainer, SortableElement } from 'react-sortable-hoc';
import { arrayMove } from 'array-move'; // Recommended
const SortableItem = SortableElement(({ value }) => <li>{value}</li>);
const SortableList = SortableContainer(({ items }) => (
<ul>
{items.map((value, index) => (
<SortableItem key={`item-${index}`} index={index} value={value} />
))}
</ul>
));
const App = () => {
const [items, setItems] = useState(['Item 1', 'Item 2', 'Item 3']);
const onSortEnd = ({ oldIndex, newIndex }) => {
setItems(arrayMove(items, oldIndex, newIndex));
};
return <SortableList items={items} onSortEnd={onSortEnd} />;
};For moving multiple selected items:
const moveMultipleItems = (array: any[], selectedIndices: number[], targetIndex: number) => {
const selectedItems = selectedIndices.map(index => array[index]);
const remainingItems = array.filter((_, index) => !selectedIndices.includes(index));
// Insert selected items at target position
const result = [...remainingItems];
result.splice(targetIndex, 0, ...selectedItems);
return result;
};
// Usage
const items = ['A', 'B', 'C', 'D', 'E'];
const moved = moveMultipleItems(items, [1, 3], 0); // Move 'B' and 'D' to start
// Result: ['B', 'D', 'A', 'C', 'E']Only move items when conditions are met:
const conditionalMove = (
array: any[],
oldIndex: number,
newIndex: number,
condition: (item: any) => boolean
) => {
const item = array[oldIndex];
if (!condition(item)) {
return array; // No change if condition fails
}
return arrayMove(array, oldIndex, newIndex);
};
// Usage
const onSortEnd = ({ oldIndex, newIndex }) => {
setItems(prevItems =>
conditionalMove(
prevItems,
oldIndex,
newIndex,
item => !item.locked // Only move unlocked items
)
);
};When working with multiple collections:
const moveWithinCollection = (
items: any[],
oldIndex: number,
newIndex: number,
collection: string
) => {
// Filter items by collection
const collectionItems = items.filter(item => item.collection === collection);
const otherItems = items.filter(item => item.collection !== collection);
// Move within collection
const reorderedCollection = arrayMove(collectionItems, oldIndex, newIndex);
// Merge back
return [...otherItems, ...reorderedCollection];
};
const onSortEnd = ({ oldIndex, newIndex, collection }) => {
setItems(prevItems =>
moveWithinCollection(prevItems, oldIndex, newIndex, collection)
);
};Manual implementation without external dependency:
const manualArrayMove = <T>(array: T[], fromIndex: number, toIndex: number): T[] => {
const result = array.slice(); // Create copy
const [removed] = result.splice(fromIndex, 1); // Remove item
result.splice(toIndex, 0, removed); // Insert at new position
return result;
};For immutable state management:
import { produce } from 'immer';
const immerArrayMove = produce((draft, fromIndex, toIndex) => {
const [removed] = draft.splice(fromIndex, 1);
draft.splice(toIndex, 0, removed);
});
// Usage
const onSortEnd = ({ oldIndex, newIndex }) => {
setItems(prevItems => immerArrayMove(prevItems, oldIndex, newIndex));
};For large arrays, consider using keys based on item IDs rather than indices:
const SortableItem = SortableElement(({ item }) => <li>{item.name}</li>);
const SortableList = SortableContainer(({ items }) => (
<ul>
{items.map((item, index) => (
<SortableItem
key={item.id} // Use stable ID, not index
index={index}
item={item}
/>
))}
</ul>
));Memoize expensive array operations:
import { useMemo } from 'react';
const App = () => {
const [items, setItems] = useState(largeItemList);
const sortedItems = useMemo(() => {
return items.sort((a, b) => a.priority - b.priority);
}, [items]);
const onSortEnd = ({ oldIndex, newIndex }) => {
setItems(prevItems => arrayMove(prevItems, oldIndex, newIndex));
};
return <SortableList items={sortedItems} onSortEnd={onSortEnd} />;
};// Old (deprecated)
import { arrayMove } from 'react-sortable-hoc';
// New (recommended)
import { arrayMove } from 'array-move';
// The API is identical, just change the import
const reordered = arrayMove(items, oldIndex, newIndex);# Install the separate package
npm install array-move
# TypeScript types are included
# No need for @types/array-moveThe standalone array-move package provides additional utilities:
import { arrayMove, arrayMoveImmutable, arrayMoveMutable } from 'array-move';
// Immutable (returns new array)
const newArray = arrayMoveImmutable([1, 2, 3], 0, 2);
// Mutable (modifies original array)
arrayMoveMutable(originalArray, 0, 2);
// Standard (same as arrayMoveImmutable)
const moved = arrayMove([1, 2, 3], 0, 2);Install with Tessl CLI
npx tessl i tessl/npm-react-sortable-hoc