From b3e9dd200290da0a2e7b97af0c9e7589b51f6ff8 Mon Sep 17 00:00:00 2001 From: Max Freedom Pollard <272618364+MaxFreedomPollard@users.noreply.github.com> Date: Sat, 5 Sep 2026 20:50:26 -0400 Subject: [PATCH] fix(query-persist-client-core/createPersister): await 'storage.setItem' when persisting a query `persistQuery` in packages/query-persist-client-core/src/createPersister.ts awaited `serialize` but not `storage.setItem`, so the promise it returns resolved as soon as the write was started rather than when it finished. Two consequences with an asynchronous storage such as React Native AsyncStorage or idb-keyval. `await persister.persistQueryByKey(queryKey, queryClient)`, which the createPersister docs show being used in `onMutate` to persist an optimistic update, returned before the entry was in storage. And a failing write, for example a quota error, surfaced as an unhandled promise rejection instead of rejecting the returned promise. `createAsyncStoragePersister` already awaits its `storage.setItem` call in packages/query-async-storage-persister/src/index.ts. Adding the `await` fixes both. The two new tests in packages/query-persist-client-core/src/__tests__/createPersister.test.ts cover the ordering and the rejection, and both fail without the change. --- .changeset/persister-await-storage-write.md | 5 ++ .../src/__tests__/createPersister.test.ts | 58 +++++++++++++++++++ .../src/createPersister.ts | 2 +- 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 .changeset/persister-await-storage-write.md diff --git a/.changeset/persister-await-storage-write.md b/.changeset/persister-await-storage-write.md new file mode 100644 index 00000000000..2827adcd2c5 --- /dev/null +++ b/.changeset/persister-await-storage-write.md @@ -0,0 +1,5 @@ +--- +'@tanstack/query-persist-client-core': patch +--- + +fix(query-persist-client-core/createPersister): await the storage write in `persistQuery`, so `persistQueryByKey` resolves only once the entry has actually been written and rejects when the write fails instead of leaving an unhandled rejection diff --git a/packages/query-persist-client-core/src/__tests__/createPersister.test.ts b/packages/query-persist-client-core/src/__tests__/createPersister.test.ts index ca0a8aeddbf..52e5278338d 100644 --- a/packages/query-persist-client-core/src/__tests__/createPersister.test.ts +++ b/packages/query-persist-client-core/src/__tests__/createPersister.test.ts @@ -521,6 +521,64 @@ describe('createPersister', () => { }, }) }) + + it('should not resolve until an asynchronous write has finished', async () => { + const storage = getFreshStorage() + let releaseWrite!: () => void + const writeGate = new Promise((resolve) => { + releaseWrite = resolve + }) + const { persister, client, queryKey, storageKey } = setupPersister( + ['foo'], + { + storage: { + ...storage, + setItem: async (key, value) => { + await writeGate + await storage.setItem(key, value) + }, + }, + }, + ) + + client.setQueryData(queryKey, 'baz') + + let settled = false + const persisted = persister + .persistQueryByKey(queryKey, client) + .then(() => { + settled = true + }) + + await vi.advanceTimersByTimeAsync(0) + + expect(settled).toBe(false) + expect(await storage.getItem(storageKey)).toBeUndefined() + + releaseWrite() + await persisted + + expect(settled).toBe(true) + expect(JSON.parse(await storage.getItem(storageKey))).toMatchObject({ + state: { data: 'baz' }, + }) + }) + + it('should reject when the write fails', async () => { + const storage = getFreshStorage() + const { persister, client, queryKey } = setupPersister(['foo'], { + storage: { + ...storage, + setItem: () => Promise.reject(new Error('storage is full')), + }, + }) + + client.setQueryData(queryKey, 'baz') + + await expect( + persister.persistQueryByKey(queryKey, client), + ).rejects.toThrow('storage is full') + }) }) describe('retrieveQuery', () => { diff --git a/packages/query-persist-client-core/src/createPersister.ts b/packages/query-persist-client-core/src/createPersister.ts index 16fa8c43104..7e3e82fb96e 100644 --- a/packages/query-persist-client-core/src/createPersister.ts +++ b/packages/query-persist-client-core/src/createPersister.ts @@ -188,7 +188,7 @@ export function experimental_createQueryPersister({ async function persistQuery(query: Query) { if (storage != null) { const storageKey = `${prefix}-${query.queryHash}` - storage.setItem( + await storage.setItem( storageKey, await serialize({ state: query.state,