diff --git a/src/lib/queries/__tests__/use-documents-layout.test.ts b/src/lib/queries/__tests__/use-documents-layout.test.ts index b7c3d01e5..93c5fd438 100644 --- a/src/lib/queries/__tests__/use-documents-layout.test.ts +++ b/src/lib/queries/__tests__/use-documents-layout.test.ts @@ -72,6 +72,41 @@ describe("runSetDocumentsLayout", () => { await run; }); + it("is not undone by a first read still in flight", async () => { + const queryClient = new QueryClient(); + let answerRead: (value: DocumentsLayout) => void = () => {}; + const read = queryClient + .fetchQuery({ + queryKey: key, + queryFn: () => + new Promise((resolve) => { + answerRead = resolve; + }), + }) + .catch(() => undefined); + let settle: (value: DocumentsLayout) => void = () => {}; + vi.mocked(apiPut).mockReturnValue( + new Promise((resolve) => { + settle = resolve; + }), + ); + + const run = runSetDocumentsLayout({ + patch: { view: "list" }, + queryClient, + t, + }); + // The read answers with the stored layout while the save is pending. + answerRead(STORED); + await read; + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(queryClient.getQueryData(key)?.view).toBe("list"); + + settle({ ...STORED, view: "list" }); + await run; + expect(queryClient.getQueryData(key)?.view).toBe("list"); + }); + it("rolls back and says so when the save fails", async () => { const queryClient = new QueryClient(); queryClient.setQueryData(key, STORED); diff --git a/src/lib/queries/use-documents-layout.ts b/src/lib/queries/use-documents-layout.ts index e9602a5d7..5b1251e0e 100644 --- a/src/lib/queries/use-documents-layout.ts +++ b/src/lib/queries/use-documents-layout.ts @@ -40,6 +40,13 @@ export async function runSetDocumentsLayout(deps: { }): Promise { const { patch, queryClient, t } = deps; const key = queryKeys.documentsLayout(); + // A read still in flight (the first GET) would land after the flip and + // paint the old layout until the PUT answered. Cancel it first; only then + // is the flip safe, because a cancelled fetch reverts the cache as it + // settles. Without one in flight the flip stays synchronous. + if (queryClient.isFetching({ queryKey: key }) > 0) { + await queryClient.cancelQueries({ queryKey: key }); + } const previous = queryClient.getQueryData(key); queryClient.setQueryData(key, { ...(previous ?? DEFAULT_DOCUMENTS_LAYOUT),