History

fix(react): sort merged source list by name so optimistic adds don't jump

mergePending prepends the pending placeholder while the server's listSources returns in insertion order, so a newly added source appeared at the top during the pending phase and jumped to the bottom once the server confirmed. Sort the merged list by name so the placeholder lands in the same position the canonical row will occupy.

Rhys Sullivan committed 6 months ago

Commit 261c4dd1 parent 96dc18a1 file · +8 −6Browse files

Unified · Split

packages/react/src/api/optimistic.tsx+8−6
@@ -93,16 +93,17 @@ export interface PendingSource { · 92 unmodified lines
9393
9494 /**
9595 * Sidebar/list helper. Reads `sourcesAtom(scopeId)` and merges any pending
96− * placeholder rows on top so the UI updates instantly when an add form pushes
97− * a placeholder.
96+ * placeholder rows in, sorting the combined list by name so the placeholder
97+ * lands in the same position the canonical row will occupy — no visual jump
98+ * when the server confirms.
9899 */
99100 export const useSourcesWithPending = (scopeId: ScopeId) => {
100101 const result = useAtomValue(sourcesAtom(scopeId));
101102 const { pending } = usePendingResource<PendingSource>(PendingResource.sources);
102103 return React.useMemo(
103104 () =>
104− Result.map(result, (sources) =>
105− mergePending(
105+ Result.map(result, (sources) => {
106+ const merged = mergePending(
106107 pending,
107108 sources,
108109 (s) => s.id,
@@ -117,8 +118,9 @@ export const useSourcesWithPending = (scopeId: ScopeId) => { · 8 unmodified lines
117118 canRefresh: false,
118119 canEdit: false,
119120 }),
120− ),
121− ),
121+ );
122+ return [...merged].sort((a, b) => a.name.localeCompare(b.name));
123+ }),
122124 [result, pending],
123125 );
124126 };