Repository navigation
Replies: 1 comment
|
import { Reorder } from 'motion/react'
import { useRef, useState } from 'react'
function MyList({ initial }: { initial: Item[] }) {
const [items, setItems] = useState(initial)
const latestOrderRef = useRef(items)
// Keep a ref in sync with state so the drag-end handler sees the newest order.
latestOrderRef.current = items
const persist = async () => {
await fetch('/api/items/reorder', {
method: 'POST',
body: JSON.stringify({ ids: latestOrderRef.current.map((i) => i.id) }),
})
}
return (
<Reorder.Group axis="y" values={items} onReorder={setItems}>
{items.map((item) => (
<Reorder.Item
key={item.id}
value={item}
onDragEnd={persist} // ← the reliable "drop" signal
>
{item.name}
</Reorder.Item>
))}
</Reorder.Group>
)
}Three subtleties worth knowing about1. Guard against no-op drops. Users sometimes start a drag, change their mind, and drop in the same spot. You probably don't want to fire an API call for that: const snapshotRef = useRef<string>('')
<Reorder.Item
key={item.id}
value={item}
onDragStart={() => { snapshotRef.current = items.map((i) => i.id).join(',') }}
onDragEnd={() => {
const next = latestOrderRef.current.map((i) => i.id).join(',')
if (next !== snapshotRef.current) persist()
}}
>2. Rollback on failure. The optimistic UI in the snippet above assumes success. Proper rollback means remembering the pre-drag order: const rollbackRef = useRef<Item[]>(items)
<Reorder.Item
onDragStart={() => { rollbackRef.current = items }}
onDragEnd={async () => {
try {
await fetch('/api/items/reorder', { /* ... */ })
} catch {
setItems(rollbackRef.current) // revert
}
}}
>3. Debounce if you also want intermediate saves. If the list is huge and the drag is long, you sometimes want to save intermediate positions so a crash mid-drag doesn't lose progress. import { useMemo } from 'react'
import { debounce } from 'es-toolkit'
const debouncedPersist = useMemo(() => debounce(persist, 300), [])
<Reorder.Group
values={items}
onReorder={(next) => {
setItems(next)
debouncedPersist()
}}
>
{items.map((item) => (
<Reorder.Item
value={item}
onDragEnd={() => {
debouncedPersist.cancel()
persist() // final flush
}}
/>
))}
</Reorder.Group>Most apps don't need this — the simple Why not
|
Uh oh!
There was an error while loading. Please reload this page.
So Reorder.Group.onReorder method calls every time item changes its position during the drag. But that doesn't mean that DnD is finished. If I continue drag from the beginning of the list to its end - I will see multiple onReorder calls. So, I cannot use onReorder as a trigger for issues a api request call.
My question is: how to reliably detect that item reached its final destination after all reorder events, and dropped, so I can persist new state of elements on the server via api call?
Thanks.
All reactions