Skip to content

Commit 9c011f6

Browse files
fix(tables): stop insert-row flicker and return order_key from rows list
The rows insert flicker came from useCreateTableRow.onSettled invalidating tableKeys.detail(tableId), which prefix-matches the nested rowsRoot rows query and forces an un-cancelled refetch on every insert. A late offset refetch could resolve after the optimistic splice and clobber freshly-inserted rows. - invalidate detail with exact:true (+ lists) so the count surfaces refresh without cascading into the rows query - compare order keys bytewise in reconcileCreatedRow to match the server's COLLATE "C" ordering and fitsAfter (was localeCompare) - include order_key in the GET /rows list response; it was dropped in the route mapping, so the client never saw keys and reconcileCreatedRow always fell back to the position path
1 parent 4f00baf commit 9c011f6

2 files changed

Lines changed: 36 additions & 2 deletions

File tree

apps/sim/app/api/table/[tableId]/rows/route.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ export const GET = withRouteHandler(
284284
data: r.data,
285285
executions: r.executions,
286286
position: r.position,
287+
orderKey: r.orderKey ?? undefined,
287288
createdAt:
288289
r.createdAt instanceof Date ? r.createdAt.toISOString() : String(r.createdAt),
289290
updatedAt:

apps/sim/hooks/queries/tables.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,26 @@ function invalidateRowCount(queryClient: ReturnType<typeof useQueryClient>, tabl
177177
queryClient.invalidateQueries({ queryKey: tableKeys.lists() })
178178
}
179179

180+
/**
181+
* Invalidate only the row-count surfaces — the table detail and the tables
182+
* list, both of which carry the unfiltered `rowCount`. Deliberately leaves
183+
* `rowsRoot` (the rows infinite query) untouched so an offset-paginated refetch
184+
* can't resolve late and clobber rows already spliced in optimistically. Use
185+
* for inserts, where `reconcileCreatedRow` is the source of truth for the rows
186+
* cache and its `totalCount`.
187+
*
188+
* `rowsRoot` is nested under `detail` (`[...detail(tableId), 'rows']`), so the
189+
* detail invalidation MUST be `exact` — a prefix match would cascade into the
190+
* rows queries and trigger the very refetch this helper exists to avoid.
191+
*/
192+
function invalidateRowCountSurfaces(
193+
queryClient: ReturnType<typeof useQueryClient>,
194+
tableId: string
195+
) {
196+
queryClient.invalidateQueries({ queryKey: tableKeys.detail(tableId), exact: true })
197+
queryClient.invalidateQueries({ queryKey: tableKeys.lists() })
198+
}
199+
180200
function invalidateTableSchema(queryClient: ReturnType<typeof useQueryClient>, tableId: string) {
181201
queryClient.invalidateQueries({ queryKey: tableKeys.detail(tableId) })
182202
queryClient.invalidateQueries({ queryKey: tableKeys.rowsRoot(tableId) })
@@ -591,7 +611,10 @@ export function useCreateTableRow({ workspaceId, tableId }: RowMutationContext)
591611
toast.error(error.message, { duration: 5000 })
592612
},
593613
onSettled: () => {
594-
invalidateRowCount(queryClient, tableId)
614+
// `reconcileCreatedRow` (onSuccess) is the source of truth for the rows
615+
// cache + its `totalCount`; only refresh the count surfaces here so a late
616+
// offset refetch can't clobber freshly-inserted rows (insert-flicker).
617+
invalidateRowCountSurfaces(queryClient, tableId)
595618
},
596619
})
597620
}
@@ -654,9 +677,19 @@ function reconcileCreatedRow(
654677
// path so un-keyed rows aren't yanked to the front by an empty-string sort.
655678
const byKey =
656679
row.orderKey != null && old.pages.every((p) => p.rows.every((r) => r.orderKey != null))
680+
// Compare order keys bytewise to match the server's `COLLATE "C"` ordering
681+
// and the `>=` checks in `fitsAfter` — `localeCompare` is locale-aware and
682+
// would place the new row in a different slot than the server (e.g. an
683+
// uppercase-prefixed key), leaving it visibly misordered until next reload.
657684
const sortRows = (rows: TableRow[]) =>
658685
byKey
659-
? [...rows].sort((a, b) => (a.orderKey as string).localeCompare(b.orderKey as string))
686+
? [...rows].sort((a, b) =>
687+
(a.orderKey as string) < (b.orderKey as string)
688+
? -1
689+
: (a.orderKey as string) > (b.orderKey as string)
690+
? 1
691+
: 0
692+
)
660693
: [...rows].sort((a, b) => a.position - b.position)
661694
const fitsAfter = (last: TableRow | undefined) =>
662695
last === undefined ||

0 commit comments

Comments
 (0)