1 | # `react-sortablejs`
|
2 |
|
3 | React bindings to [SortableJS](https://github.com/SortableJS/Sortable)
|
4 |
|
5 | ## Features
|
6 |
|
7 | ## Installation
|
8 |
|
9 | `sortablejs` and `@types/sortbalejs` are peer dependencies. The latter only used if intellisense/typescript is desired.
|
10 |
|
11 | ```shell
|
12 | npm install --save react-sortablejs sortablejs
|
13 | npm install --save-dev @types/sortablejs
|
14 |
|
15 | # OR
|
16 | yarn add react-sortablejs sortablejs
|
17 | yarn add -D @types/sortablejs
|
18 | ```
|
19 |
|
20 | ## Learn
|
21 |
|
22 | Here is the TLDR of what sortable is:
|
23 |
|
24 | ```md
|
25 | - Shopping List: # list of items / sortable. This represents `react-sortablejs`
|
26 | - eggs # list item. These are all the items in the list and is what you move around.
|
27 | - bread # list item
|
28 | - milk # list item
|
29 | ```
|
30 |
|
31 | ## Usage/Examples
|
32 |
|
33 | ### Function Component
|
34 |
|
35 | ```tsx
|
36 | import React, { FC, useState } from "react";
|
37 | import { ReactSortable } from "react-sortablejs";
|
38 |
|
39 | interface ItemType {
|
40 | id: number;
|
41 | name: string;
|
42 | }
|
43 |
|
44 | export const BasicFunction: FC = (props) => {
|
45 | const [state, setState] = useState<ItemType[]>([
|
46 | { id: 1, name: "shrek" },
|
47 | { id: 2, name: "fiona" },
|
48 | ]);
|
49 |
|
50 | return (
|
51 | <ReactSortable list={state} setList={setState}>
|
52 | {state.map((item) => (
|
53 | <div key={item.id}>{item.name}</div>
|
54 | ))}
|
55 | </ReactSortable>
|
56 | );
|
57 | };
|
58 | ```
|
59 |
|
60 | ### Class Component
|
61 |
|
62 | ```tsx
|
63 | import React, { Component } from "react";
|
64 | import { ReactSortable } from "react-sortablejs";
|
65 |
|
66 | interface BasicClassState {
|
67 | list: { id: string; name: string }[];
|
68 | }
|
69 |
|
70 | export class BasicClass extends Component<{}, BasicClassState> {
|
71 | state: BasicClassState = {
|
72 | list: [{ id: "1", name: "shrek" }],
|
73 | };
|
74 | render() {
|
75 | return (
|
76 | <ReactSortable
|
77 | list={this.state.list}
|
78 | setList={(newState) => this.setState({ list: newState })}
|
79 | >
|
80 | {this.state.list.map((item) => (
|
81 | <div key={item.id}>{item.name}</div>
|
82 | ))}
|
83 | </ReactSortable>
|
84 | );
|
85 | }
|
86 | }
|
87 | ```
|
88 |
|
89 | ## Plugins
|
90 |
|
91 | Sortable has some pretty cool plugins such as MultiDrag and Swap.
|
92 |
|
93 | By Default:
|
94 |
|
95 | - AutoScroll is premounted and enabled.
|
96 | - OnSpill is premounted and NOT enabled.
|
97 | - MultiDrag and Swap and NOT premounted and NOT enabled
|
98 |
|
99 | You must mount mount the plugin with sortable **ONCE ONLY**.
|
100 |
|
101 | ```tsx
|
102 | import React from "react";
|
103 | import { ReactSortable, Sortable, MultiDrag, Swap } from "react-sortablejs";
|
104 |
|
105 | // mount whatever plugins you'd like to. These are the only current options.
|
106 | Sortable.mount(new MultiDrag(), new Swap());
|
107 |
|
108 | const App = () => {
|
109 | const [state, setState] = useState([
|
110 | { id: 1, name: "shrek" },
|
111 | { id: 2, name: "fiona" },
|
112 | ]);
|
113 |
|
114 | return (
|
115 | <ReactSortable
|
116 | multiDrag // enables mutidrag
|
117 | // OR
|
118 | swap // enables swap
|
119 | >
|
120 | {state.map((item) => (
|
121 | <div key={item.id}>{item.name}</div>
|
122 | ))}
|
123 | </ReactSortable>
|
124 | );
|
125 | };
|
126 | ```
|
127 |
|
128 | ## Sortable API
|
129 |
|
130 | For a comprehensive list of options, please visit https://github.com/SortableJS/Sortable#options.
|
131 |
|
132 | Those options are applied as follows.
|
133 |
|
134 | ```tsx
|
135 | Sortable.create(element, {
|
136 | group: " groupName",
|
137 | animation: 200,
|
138 | delayOnTouchStart: true,
|
139 | delay: 2,
|
140 | });
|
141 |
|
142 | // --------------------------
|
143 | // Will now be..
|
144 | // --------------------------
|
145 |
|
146 | import React from "react";
|
147 | import { ReactSortable } from "react-sortablejs";
|
148 |
|
149 | const App = () => {
|
150 | const [state, setState] = useState([
|
151 | { id: 1, name: "shrek" },
|
152 | { id: 2, name: "fiona" },
|
153 | ]);
|
154 |
|
155 | return (
|
156 | <ReactSortable
|
157 | // here they are!
|
158 | group="groupName"
|
159 | animation={200}
|
160 | delayOnTouchStart={true}
|
161 | delay={2}
|
162 | >
|
163 | {state.map((item) => (
|
164 | <div key={item.id}>{item.name}</div>
|
165 | ))}
|
166 | </ReactSortable>
|
167 | );
|
168 | };
|
169 | ```
|
170 |
|
171 | ## React API
|
172 |
|
173 | ### id, className, style
|
174 |
|
175 | Thes are all defaults DOM attributes. Nothing special here.
|
176 |
|
177 | ### list
|
178 |
|
179 | The same as `state` in `const [ state, setState] = useState([{ id: 1}, {id: 2}])`
|
180 |
|
181 | `state` must be an array of items, with each item being an object that has the following shape:
|
182 |
|
183 | ```ts
|
184 | /** The unique id associated with your item. It's recommended this is the same as the key prop for your list item. */
|
185 | id: string | number;
|
186 | /** When true, the item is selected using MultiDrag */
|
187 | selected?: boolean;
|
188 | /** When true, the item is deemed "chosen", which basically just a mousedown event. */
|
189 | chosen?: boolean;
|
190 | /** When true, it will not be possible to pick this item up in the list. */
|
191 | filtered?: boolean;
|
192 | [property: string]: any;
|
193 | ```
|
194 |
|
195 | ### setList
|
196 |
|
197 | The same as `setState` in `const [ state, setState] = useState([{ id: 1}, {id: 2}])`
|
198 |
|
199 | ### clone
|
200 |
|
201 | If you're using `{group: { name: 'groupName', pull: 'clone'}}`, this means your in 'clone' mode. You should provide a function for this.
|
202 |
|
203 | Check out the source code of the clone example for more information. I'll write it here soon.
|
204 |
|
205 | ### tag
|
206 |
|
207 | ReactSortable is a `div` element by default. This can be changed to be any HTML element (for example `ul`, `ol`)
|
208 | or can be a React component.
|
209 |
|
210 | This value, be the component or the HTML element should be passed down under `props.tag`.
|
211 |
|
212 | Let's explore both here.
|
213 |
|
214 | #### HTML Element
|
215 |
|
216 | Here we will use a `ul`. You can use any HTML.
|
217 | Just add the string and ReactSortable will use a `li` instead of a `div`.
|
218 |
|
219 | ```tsx
|
220 | import React, { FC, useState } from "react";
|
221 | import { ReactSortable } from "react-sortablejs";
|
222 |
|
223 | export const BasicFunction: FC = (props) => {
|
224 | const [state, setState] = useState([{ id: "1", name: "shrek" }]);
|
225 |
|
226 | return (
|
227 | <ReactSortable tag="ul" list={state} setList={setState}>
|
228 | {state.map((item) => (
|
229 | <li key={item.id}>{item.name}</li>
|
230 | ))}
|
231 | </ReactSortable>
|
232 | );
|
233 | };
|
234 | ```
|
235 |
|
236 | #### Custom Component
|
237 |
|
238 | When using a custom component in the `tag` prop, the only component it allows is a `forwardRef` component.
|
239 | Currently we only support components who use the `React.forwardRef` API.
|
240 |
|
241 | If it doesn't have one, you can add one using `React.forwardRef()`.
|
242 |
|
243 | > todo: Some third party UI components may have nested elements to create the look they're after.
|
244 | > This could be an issue and not sure how to fix.
|
245 |
|
246 | ```tsx
|
247 | import React, { FC, useState, forwardRef } from "react";
|
248 | import { ReactSortable } from "react-sortablejs";
|
249 |
|
250 | // This is just like a normal component, but now has a ref.
|
251 | const CustomComponent = forwardRef<HTMLDivElement, any>((props, ref) => {
|
252 | return <div ref={ref}>{props.children}</div>;
|
253 | });
|
254 |
|
255 | export const BasicFunction: FC = (props) => {
|
256 | const [state, setState] = useState([
|
257 | { id: 1, name: "shrek" },
|
258 | { id: 2, name: "fiona" },
|
259 | ]);
|
260 |
|
261 | return (
|
262 | <ReactSortable tag={CustomComponent} list={state} setList={setState}>
|
263 | {state.map((item) => (
|
264 | <div key={item.id}>{item.name}</div>
|
265 | ))}
|
266 | </ReactSortable>
|
267 | );
|
268 | };
|
269 | ```
|
270 |
|
271 | ## How does it work?
|
272 |
|
273 | Sortable affects the DOM, adding, and removing nodes/css when it needs to in order to achieve the smooth transitions we all know an love.
|
274 | This component reverses many of it's actions of the DOM so React can handle this when the state changes.
|
275 |
|
276 | ## Caveats / Gotchas
|
277 |
|
278 | ### `key !== index`
|
279 |
|
280 | DO NOT use the index as a key for your list items. Sorting will not work.
|
281 |
|
282 | In all the examples above, I used an object with an ID. You should do the same!
|
283 |
|
284 | I may even enforce this into the design to eliminate errors.
|
285 |
|
286 | ### Nesting
|
287 |
|
288 | #### Problem
|
289 |
|
290 | Basically the child updates the state twice. I'm working on this.
|
291 |
|
292 | #### What does work?
|
293 |
|
294 | Our usage indicates that as long as we only move items between lists that don't use the same `setState` function.
|
295 |
|
296 | I hope to provide an example soon.
|
297 |
|
298 | #### Solutions
|
299 |
|
300 | We don't have anything that works 100%, but here I'd like to spit ball some potential avenues to look down.
|
301 |
|
302 | - Use `onMove` to handle state changes instead of `onAdd`,`onRemove`, etc.
|
303 | - Create a Sortable plugin specifically for react-sortbalejs
|