From 10ec81ddb5db6346942348cf0840cc1bc8334dd6 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 13 May 2026 18:56:09 -0700 Subject: [PATCH 1/3] fix(tables): eliminate checkbox flicker on rapid cell toggle --- apps/sim/hooks/queries/tables.ts | 34 ++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/apps/sim/hooks/queries/tables.ts b/apps/sim/hooks/queries/tables.ts index bc8a30840a..4b0299a4f0 100644 --- a/apps/sim/hooks/queries/tables.ts +++ b/apps/sim/hooks/queries/tables.ts @@ -85,6 +85,7 @@ export const tableKeys = { rowsRoot: (tableId: string) => [...tableKeys.detail(tableId), 'rows'] as const, infiniteRows: (tableId: string, paramsKey: string) => [...tableKeys.rowsRoot(tableId), 'infinite', paramsKey] as const, + rowWrites: (tableId: string) => [...tableKeys.rowsRoot(tableId), 'write'] as const, } type TableRowsParams = Omit & @@ -543,14 +544,15 @@ export function useUpdateTableRow({ workspaceId, tableId }: RowMutationContext) const queryClient = useQueryClient() return useMutation({ + mutationKey: tableKeys.rowWrites(tableId), mutationFn: async ({ rowId, data }: UpdateTableRowParams) => { return requestJson(updateTableRowContract, { params: { tableId, rowId }, body: { workspaceId, data: data as RowData }, }) }, - onMutate: ({ rowId, data }) => { - void queryClient.cancelQueries({ queryKey: tableKeys.rowsRoot(tableId) }) + onMutate: async ({ rowId, data }) => { + await queryClient.cancelQueries({ queryKey: tableKeys.rowsRoot(tableId) }) const previousQueries = queryClient.getQueriesData>({ queryKey: tableKeys.rowsRoot(tableId), @@ -573,18 +575,31 @@ export function useUpdateTableRow({ workspaceId, tableId }: RowMutationContext) return { previousQueries } }, + onSuccess: (response, { rowId }) => { + const serverRow = response.data.row + patchCachedRows(queryClient, tableId, (row) => { + if (row.id !== rowId) return row + return { + ...row, + data: serverRow.data, + position: serverRow.position, + createdAt: serverRow.createdAt, + updatedAt: serverRow.updatedAt, + } + }) + }, onError: (error, _vars, context) => { if (context?.previousQueries) { for (const [queryKey, data] of context.previousQueries) { queryClient.setQueryData(queryKey, data) } } + if (queryClient.isMutating({ mutationKey: tableKeys.rowWrites(tableId) }) === 1) { + invalidateRowData(queryClient, tableId) + } if (isValidationError(error)) return toast.error(error.message, { duration: 5000 }) }, - onSettled: () => { - invalidateRowData(queryClient, tableId) - }, }) } @@ -599,6 +614,7 @@ export function useBatchUpdateTableRows({ workspaceId, tableId }: RowMutationCon const queryClient = useQueryClient() return useMutation({ + mutationKey: tableKeys.rowWrites(tableId), mutationFn: async ({ updates }: BatchUpdateTableRowsParams) => { return requestJson(batchUpdateTableRowsContract, { params: { tableId }, @@ -608,8 +624,8 @@ export function useBatchUpdateTableRows({ workspaceId, tableId }: RowMutationCon }, }) }, - onMutate: ({ updates }) => { - void queryClient.cancelQueries({ queryKey: tableKeys.rowsRoot(tableId) }) + onMutate: async ({ updates }) => { + await queryClient.cancelQueries({ queryKey: tableKeys.rowsRoot(tableId) }) const previousQueries = queryClient.getQueriesData>({ queryKey: tableKeys.rowsRoot(tableId), @@ -644,7 +660,9 @@ export function useBatchUpdateTableRows({ workspaceId, tableId }: RowMutationCon toast.error(error.message, { duration: 5000 }) }, onSettled: () => { - invalidateRowData(queryClient, tableId) + if (queryClient.isMutating({ mutationKey: tableKeys.rowWrites(tableId) }) === 1) { + invalidateRowData(queryClient, tableId) + } }, }) } From a0974d6980aa10ac8dcc65246ec4407ef1cf6fc6 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 13 May 2026 19:15:36 -0700 Subject: [PATCH 2/3] fix(tables): symmetric guarded onSettled across row write mutations --- apps/sim/hooks/queries/tables.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/sim/hooks/queries/tables.ts b/apps/sim/hooks/queries/tables.ts index 4b0299a4f0..640ca67034 100644 --- a/apps/sim/hooks/queries/tables.ts +++ b/apps/sim/hooks/queries/tables.ts @@ -594,11 +594,13 @@ export function useUpdateTableRow({ workspaceId, tableId }: RowMutationContext) queryClient.setQueryData(queryKey, data) } } + if (isValidationError(error)) return + toast.error(error.message, { duration: 5000 }) + }, + onSettled: () => { if (queryClient.isMutating({ mutationKey: tableKeys.rowWrites(tableId) }) === 1) { invalidateRowData(queryClient, tableId) } - if (isValidationError(error)) return - toast.error(error.message, { duration: 5000 }) }, }) } From 944d7dd6409016c7e0a102238def3c09fa64afdd Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 13 May 2026 19:41:34 -0700 Subject: [PATCH 3/3] fix(tables): merge only mutated keys in onSuccess to preserve concurrent optimistic patches --- apps/sim/hooks/queries/tables.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/sim/hooks/queries/tables.ts b/apps/sim/hooks/queries/tables.ts index 640ca67034..f7b94e8b9f 100644 --- a/apps/sim/hooks/queries/tables.ts +++ b/apps/sim/hooks/queries/tables.ts @@ -575,13 +575,18 @@ export function useUpdateTableRow({ workspaceId, tableId }: RowMutationContext) return { previousQueries } }, - onSuccess: (response, { rowId }) => { + onSuccess: (response, { rowId, data: mutatedData }) => { const serverRow = response.data.row + const mutatedKeys = Object.keys(mutatedData) patchCachedRows(queryClient, tableId, (row) => { if (row.id !== rowId) return row + const merged: RowData = { ...row.data } + for (const key of mutatedKeys) { + merged[key] = (serverRow.data as RowData)[key] + } return { ...row, - data: serverRow.data, + data: merged, position: serverRow.position, createdAt: serverRow.createdAt, updatedAt: serverRow.updatedAt,