admin: fix drink-order reorder race under rapid clicks
BarDrinkEditor derived selected drink order straight from the bar prop, which only refreshes once a fire-and-forget PATCH's reload() lands. A second reorder/add/remove click before that round-trip completed re-derived from the same stale array the first click started from, so whichever PATCH the server applied last silently won and discarded the rest. Track our own in-flight edits in local pendingIds state so back-to-back clicks chain off each other instead of the lagging prop; it resets to null (defer to the prop) whenever a fresh bar.drink_ids comes back, whether that's our own round-trip landing or an edit from elsewhere.
This commit is contained in:
parent
f576fbde3e
commit
a589f88cd8
1 changed files with 24 additions and 4 deletions
|
|
@ -211,7 +211,23 @@ function BarDrinkEditor({
|
|||
onChange: (ids: number[]) => void;
|
||||
}) {
|
||||
const byId = new Map(allDrinks.map(d => [d.id, d]));
|
||||
const selected = bar.drink_ids.filter(id => {
|
||||
|
||||
// `onChange` fires a fire-and-forget PATCH (see Bars.patch) that only
|
||||
// resolves into a fresh `bar` prop once its `reload()` completes. Deriving
|
||||
// `selected` straight from `bar.drink_ids` meant a second click before that
|
||||
// round-trip landed re-derived its reorder/add/remove from the same stale
|
||||
// array the first click started from — whichever PATCH the server applied
|
||||
// last won, silently discarding the rest. `pendingIds` tracks our own
|
||||
// in-flight optimistic state so back-to-back clicks chain off each other
|
||||
// instead of the lagging prop; it's cleared whenever the server sends back
|
||||
// a fresh `bar.drink_ids` (our own round-trip landing, or an edit from
|
||||
// elsewhere), deferring to that as the new source of truth.
|
||||
const [pendingIds, setPendingIds] = useState<number[] | null>(null);
|
||||
useEffect(() => {
|
||||
setPendingIds(null);
|
||||
}, [bar.drink_ids]);
|
||||
|
||||
const selected = (pendingIds ?? bar.drink_ids).filter(id => {
|
||||
const d = byId.get(id);
|
||||
return d && !d.archived;
|
||||
});
|
||||
|
|
@ -220,18 +236,22 @@ function BarDrinkEditor({
|
|||
const dragFrom = useRef<number | null>(null);
|
||||
const [dragOver, setDragOver] = useState<number | null>(null);
|
||||
|
||||
function commit(next: number[]) {
|
||||
setPendingIds(next);
|
||||
onChange(next);
|
||||
}
|
||||
function remove(idx: number) {
|
||||
onChange(selected.filter((_, i) => i !== idx));
|
||||
commit(selected.filter((_, i) => i !== idx));
|
||||
}
|
||||
function add(id: number) {
|
||||
onChange([...selected, id]);
|
||||
commit([...selected, id]);
|
||||
}
|
||||
function reorder(from: number, to: number) {
|
||||
if (from === to || from < 0 || to < 0 || from >= selected.length || to >= selected.length) return;
|
||||
const next = selected.slice();
|
||||
const [moved] = next.splice(from, 1);
|
||||
next.splice(to, 0, moved!);
|
||||
onChange(next);
|
||||
commit(next);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
|||
Loading…
Reference in a new issue