The previous handler called router.refresh() after every successful move and the Board effect then adopted the new initialCards prop. Because router.refresh() schedules the server re-render asynchronously, the new initialCards could land AFTER setSaving(false) had already cleared busy. The effect then ran with !busy and setCards(initialCards) reverted the optimistic move — the card visibly snapped back to its origin column on every single drag, even though the database had the move. The user had to drag the card a second time to land it in the destination. Trust the optimistic state for the lifetime of the Board instance. The first mount still adopts the server snapshot via the useState initializer. Concurrent moves from other clients appear on the next page visit, which is acceptable for an owner-only kanban. The useRefreshOnFocus hook still keeps a focus listener registered while not busy, so a real cross-tab refresh still fires. |
||
|---|---|---|
| .. | ||
| app | ||
| components | ||
| lib | ||
| middleware.ts | ||