SortableJS 利用時に起きる「端だけ戻る」不具合を避ける
SortableJS で並び替えを実装したとき、ログ上は順序変更が進んでいるように見えても、画面更新後に先頭・末尾だけ元に戻ることがあります。原因は多くの場合、ライブラリそのものではなく、保存・復元フローの設計です。
よく起こる症状
- 縦ツリーの先頭/末尾だけ順序が戻る
- 横並びで左右端だけ戻る
-
sortable_reorderなどのログは出るのに UI が元に戻る
原因の本質
- SortableJS は「DOM の移動」を起点としてイベントを発生させる
- ただし最終表示は
state(内部状態)を経由して再描画される - 保存順と内部状態の反映が経路ごとに分裂すると、再読込時に整合しやすい
- ツリー構造では特に、親グループを跨ぐ比較ソートより、対象親の子配列を再構成したほうが安全
対策の原則
- 並び替え確定時は必ず
orderedPathsを1種類の順序として確定する -
onEnd, edge drop, 補助移動など、経路が違っても同一の状態更新関数へ集約する - 端点移動も
orderedPathsに含め、親単位で state を再構成する - 再読込時は保存順を同じ再構成関数で state に反映してから描画する
実装例(要点)
function applyOrderToState(parent, orderedPaths) {
const siblings = state.folderTree.filter((entry) => (entry.parent || "") === parent);
const byPath = new Map(siblings.map((entry) => [entry.path, entry]));
const ordered = [];
const used = new Set();
for (const path of orderedPaths) {
if (!byPath.has(path) || used.has(path)) continue;
ordered.push(byPath.get(path));
used.add(path);
}
for (const entry of siblings) {
if (!used.has(entry.path)) ordered.push(entry);
}
const nextTree = [];
let inserted = false;
for (const entry of state.folderTree) {
if ((entry.parent || "") === parent) {
if (!inserted) {
nextTree.push(...ordered);
inserted = true;
}
continue;
}
nextTree.push(entry);
}
if (!inserted) nextTree.push(...ordered);
state.folderTree = nextTree;
}
まとめ
端だけ戻る不具合は、SortableJS の検知失敗というより 保存→state反映→再描画の整合性不足 が本体です。
並び順は orderedPaths を唯一の正として扱い、すべての経路を同一関数で state に反映・復元する設計にすると再発を抑えられます。