0
# Sortable Element
1
2
Higher-order component that makes individual React components sortable within a SortableContainer. Provides simple interface for element positioning, grouping, and state management.
3
4
## Capabilities
5
6
### SortableElement HOC
7
8
Makes any React component sortable within a SortableContainer.
9
10
```typescript { .api }
11
/**
12
* Higher-order component that makes an element sortable within a SortableContainer
13
* @param wrappedComponent - The React component to enhance with sortable functionality
14
* @param config - Optional configuration object
15
* @returns Enhanced React component with sortable element capabilities
16
*/
17
function SortableElement<P>(
18
wrappedComponent: WrappedComponent<P>,
19
config?: Config
20
): React.ComponentClass<P & SortableElementProps>;
21
22
interface SortableElementProps {
23
/** Element's sort index within its collection (required) */
24
index: number;
25
/** Collection identifier for grouping elements */
26
collection?: Offset;
27
/** Whether the element should be sortable */
28
disabled?: boolean;
29
}
30
31
interface Config {
32
withRef: boolean;
33
}
34
```
35
36
**Usage Examples:**
37
38
```typescript
39
import React from 'react';
40
import { SortableElement } from 'react-sortable-hoc';
41
42
// Basic sortable item
43
const SortableItem = SortableElement(({ value }) => <li>{value}</li>);
44
45
// More complex sortable component
46
const SortableCard = SortableElement(({ title, description, onEdit }) => (
47
<div className="card">
48
<h3>{title}</h3>
49
<p>{description}</p>
50
<button onClick={onEdit}>Edit</button>
51
</div>
52
));
53
54
// Usage in container
55
const items = ['Item 1', 'Item 2', 'Item 3'];
56
{items.map((value, index) => (
57
<SortableItem
58
key={`item-${index}`}
59
index={index}
60
value={value}
61
/>
62
))}
63
```
64
65
### Required Props
66
67
#### Index Property
68
69
```typescript { .api }
70
/** Element's sortable index within its collection (required) */
71
index: number;
72
```
73
74
The `index` prop is required and determines the element's position in the sortable sequence. It must be unique within the collection.
75
76
**Index Examples:**
77
78
```typescript
79
// Basic sequential indexing
80
{items.map((item, index) => (
81
<SortableItem key={item.id} index={index} value={item.value} />
82
))}
83
84
// Custom indexing for sparse arrays
85
const sparseItems = [
86
{ id: 'a', value: 'First', sortOrder: 0 },
87
{ id: 'b', value: 'Second', sortOrder: 2 },
88
{ id: 'c', value: 'Third', sortOrder: 5 },
89
];
90
91
{sparseItems.map((item) => (
92
<SortableItem
93
key={item.id}
94
index={item.sortOrder}
95
value={item.value}
96
/>
97
))}
98
```
99
100
### Optional Props
101
102
#### Collection Property
103
104
```typescript { .api }
105
/** Collection identifier for grouping elements */
106
collection?: number | string; // default: 0
107
```
108
109
The `collection` prop groups elements into separate sortable areas within the same container. Useful for multi-list scenarios or categorized sorting.
110
111
**Collection Examples:**
112
113
```typescript
114
// Multiple lists in same container
115
const todoItems = [
116
{ id: 1, text: 'Task 1', status: 'pending' },
117
{ id: 2, text: 'Task 2', status: 'completed' },
118
{ id: 3, text: 'Task 3', status: 'pending' },
119
];
120
121
{todoItems.map((item, index) => (
122
<SortableTask
123
key={item.id}
124
index={index}
125
collection={item.status} // Group by status
126
task={item}
127
/>
128
))}
129
130
// Numeric collections
131
{leftColumnItems.map((item, index) => (
132
<SortableItem key={item.id} index={index} collection={0} value={item} />
133
))}
134
{rightColumnItems.map((item, index) => (
135
<SortableItem key={item.id} index={index} collection={1} value={item} />
136
))}
137
```
138
139
#### Disabled Property
140
141
```typescript { .api }
142
/** Whether the element should be sortable */
143
disabled?: boolean; // default: false
144
```
145
146
When `disabled` is true, the element cannot be dragged or participate in sorting operations.
147
148
**Disabled Examples:**
149
150
```typescript
151
// Conditionally disable elements
152
{items.map((item, index) => (
153
<SortableItem
154
key={item.id}
155
index={index}
156
value={item.value}
157
disabled={item.locked || item.readonly}
158
/>
159
))}
160
161
// Disable based on user permissions
162
{items.map((item, index) => (
163
<SortableItem
164
key={item.id}
165
index={index}
166
value={item.value}
167
disabled={!userCanSort}
168
/>
169
))}
170
```
171
172
### Component Integration
173
174
#### Passing Props Through
175
176
All props except `index`, `collection`, and `disabled` are passed through to the wrapped component.
177
178
```typescript
179
const SortableProductCard = SortableElement(({
180
product,
181
onAddToCart,
182
onViewDetails,
183
className
184
}) => (
185
<div className={`product-card ${className}`}>
186
<img src={product.image} alt={product.name} />
187
<h3>{product.name}</h3>
188
<p>${product.price}</p>
189
<button onClick={() => onAddToCart(product)}>Add to Cart</button>
190
<button onClick={() => onViewDetails(product)}>View Details</button>
191
</div>
192
));
193
194
// Usage with additional props
195
<SortableProductCard
196
key={product.id}
197
index={index}
198
product={product}
199
className="featured"
200
onAddToCart={handleAddToCart}
201
onViewDetails={handleViewDetails}
202
/>
203
```
204
205
#### Prop Renaming Pattern
206
207
To access sortable-specific props in your component, pass them with different names:
208
209
```typescript
210
const SortableListItem = SortableElement(({
211
value,
212
sortIndex, // Renamed from index
213
isDisabled // Renamed from disabled
214
}) => (
215
<li className={isDisabled ? 'disabled' : ''}>
216
{value} - Position: {sortIndex}
217
</li>
218
));
219
220
// Usage
221
<SortableListItem
222
key={item.id}
223
index={index}
224
sortIndex={index} // Pass index again with different name
225
disabled={item.locked}
226
isDisabled={item.locked} // Pass disabled again with different name
227
value={item.value}
228
/>
229
```
230
231
### Event Handling
232
233
Sortable elements can contain interactive elements, but event handling requires careful consideration:
234
235
```typescript
236
const InteractiveSortableItem = SortableElement(({
237
item,
238
onEdit,
239
onDelete
240
}) => (
241
<div className="sortable-item">
242
<span>{item.title}</span>
243
<div className="actions">
244
<button
245
onClick={(e) => {
246
e.preventDefault(); // Prevent drag
247
onEdit(item);
248
}}
249
>
250
Edit
251
</button>
252
<button
253
onClick={(e) => {
254
e.preventDefault(); // Prevent drag
255
onDelete(item);
256
}}
257
>
258
Delete
259
</button>
260
</div>
261
</div>
262
));
263
```
264
265
### withRef Configuration
266
267
```typescript { .api }
268
interface Config {
269
/** Enable access to wrapped component instance */
270
withRef: boolean;
271
}
272
```
273
274
**withRef Example:**
275
276
```typescript
277
const SortableItem = SortableElement(ItemComponent, { withRef: true });
278
279
// Access wrapped instance
280
const itemRef = useRef();
281
const wrappedInstance = itemRef.current?.getWrappedInstance();
282
283
<SortableItem
284
ref={itemRef}
285
index={index}
286
value={value}
287
/>
288
```
289
290
### Advanced Patterns
291
292
#### Dynamic Collections
293
294
```typescript
295
const [items, setItems] = useState([
296
{ id: 1, text: 'Item 1', category: 'A' },
297
{ id: 2, text: 'Item 2', category: 'B' },
298
{ id: 3, text: 'Item 3', category: 'A' },
299
]);
300
301
const handleSortEnd = ({ oldIndex, newIndex, collection }) => {
302
// Sort within specific collection
303
const categoryItems = items.filter(item => item.category === collection);
304
const otherItems = items.filter(item => item.category !== collection);
305
306
const sortedCategoryItems = arrayMove(categoryItems, oldIndex, newIndex);
307
setItems([...otherItems, ...sortedCategoryItems]);
308
};
309
310
{items.map((item, index) => (
311
<SortableItem
312
key={item.id}
313
index={items.filter(i => i.category === item.category).indexOf(item)}
314
collection={item.category}
315
value={item.text}
316
/>
317
))}
318
```
319
320
#### Conditional Rendering
321
322
```typescript
323
const ConditionalSortableItem = ({ item, index, showDetails }) => {
324
const ItemComponent = ({ value, details }) => (
325
<div className="item">
326
<span>{value}</span>
327
{showDetails && <div className="details">{details}</div>}
328
</div>
329
);
330
331
const SortableItemComponent = SortableElement(ItemComponent);
332
333
return (
334
<SortableItemComponent
335
index={index}
336
value={item.title}
337
details={item.description}
338
/>
339
);
340
};
341
```