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
93 93
94/**94/**
95 * Sidebar/list helper. Reads `sourcesAtom(scopeId)` and merges any pending95 * Sidebar/list helper. Reads `sourcesAtom(scopeId)` and merges any pending
96 * placeholder rows on top so the UI updates instantly when an add form pushes96 * placeholder rows in, sorting the combined list by name so the placeholder
97 * a placeholder.97 * lands in the same position the canonical row will occupy — no visual jump
98 * when the server confirms.
98 */99 */
99export const useSourcesWithPending = (scopeId: ScopeId) => {100export const useSourcesWithPending = (scopeId: ScopeId) => {
100 const result = useAtomValue(sourcesAtom(scopeId));101 const result = useAtomValue(sourcesAtom(scopeId));
101 const { pending } = usePendingResource<PendingSource>(PendingResource.sources);102 const { pending } = usePendingResource<PendingSource>(PendingResource.sources);
102 return React.useMemo(103 return React.useMemo(
103 () =>104 () =>
104 Result.map(result, (sources) =>105 Result.map(result, (sources) => {
105 mergePending(106 const merged = mergePending(
106 pending,107 pending,
107 sources,108 sources,
108 (s) => s.id,109 (s) => s.id,
@@ -117,8 +118,9 @@ export const useSourcesWithPending = (scopeId: ScopeId) => { · 8 unmodified lines
117 canRefresh: false,118 canRefresh: false,
118 canEdit: false,119 canEdit: false,
119 }),120 }),
120 ),121 );
121 ),122 return [...merged].sort((a, b) => a.name.localeCompare(b.name));
123 }),
122 [result, pending],124 [result, pending],
123 );125 );
124};126};