Skip to content

Commit 2ccdcb2

Browse files
committed
feat: mutation cachetime
replicate gc behavior from TanStack#2950 and add more tests
1 parent a5ce8f9 commit 2ccdcb2

2 files changed

Lines changed: 55 additions & 2 deletions

File tree

src/core/mutation.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,12 @@ export class Mutation<
145145
}
146146

147147
protected optionalRemove() {
148-
if (!this.observers.length && this.state.status !== 'loading') {
149-
this.mutationCache.remove(this)
148+
if (!this.observers.length) {
149+
if (this.state.status === 'loading') {
150+
this.scheduleGc()
151+
} else {
152+
this.mutationCache.remove(this)
153+
}
150154
}
151155
}
152156

src/core/tests/mutationCache.test.tsx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { waitFor } from '@testing-library/react'
12
import { queryKey, mockConsoleError, sleep } from '../../react/tests/utils'
23
import { MutationCache, MutationObserver, QueryClient } from '../..'
34

@@ -111,16 +112,19 @@ describe('mutationCache', () => {
111112
test('should remove unused mutations after cacheTime has elapsed', async () => {
112113
const testCache = new MutationCache()
113114
const testClient = new QueryClient({ mutationCache: testCache })
115+
const onSuccess = jest.fn()
114116
await testClient.executeMutation({
115117
mutationKey: ['a', 1],
116118
variables: 1,
117119
cacheTime: 10,
118120
mutationFn: () => Promise.resolve(),
121+
onSuccess,
119122
})
120123

121124
expect(testCache.getAll()).toHaveLength(1)
122125
await sleep(10)
123126
expect(testCache.getAll()).toHaveLength(0)
127+
expect(onSuccess).toHaveBeenCalledTimes(1)
124128
})
125129

126130
test('should not remove mutations if there are active observers', async () => {
@@ -142,5 +146,50 @@ describe('mutationCache', () => {
142146
await sleep(10)
143147
expect(queryClient.getMutationCache().getAll()).toHaveLength(0)
144148
})
149+
150+
test('should be garbage collected later when unsubscribed and mutation is loading', async () => {
151+
const queryClient = new QueryClient()
152+
const onSuccess = jest.fn()
153+
const observer = new MutationObserver(queryClient, {
154+
variables: 1,
155+
cacheTime: 10,
156+
mutationFn: async () => {
157+
await sleep(20)
158+
return 'data'
159+
},
160+
onSuccess,
161+
})
162+
const unsubscribe = observer.subscribe()
163+
observer.mutate(1)
164+
unsubscribe()
165+
expect(queryClient.getMutationCache().getAll()).toHaveLength(1)
166+
await sleep(10)
167+
// unsubscribe should not remove even though cacheTime has elapsed b/c mutation is still loading
168+
expect(queryClient.getMutationCache().getAll()).toHaveLength(1)
169+
await sleep(10)
170+
// should be removed after an additional cacheTime wait
171+
expect(queryClient.getMutationCache().getAll()).toHaveLength(0)
172+
expect(onSuccess).toHaveBeenCalledTimes(1)
173+
})
174+
175+
test('should call callbacks even with cacheTime 0 and mutation still loading', async () => {
176+
const queryClient = new QueryClient()
177+
const onSuccess = jest.fn()
178+
const observer = new MutationObserver(queryClient, {
179+
variables: 1,
180+
cacheTime: 0,
181+
mutationFn: async () => {
182+
return 'data'
183+
},
184+
onSuccess,
185+
})
186+
const unsubscribe = observer.subscribe()
187+
observer.mutate(1)
188+
unsubscribe()
189+
await waitFor(() => {
190+
expect(queryClient.getMutationCache().getAll()).toHaveLength(0)
191+
})
192+
expect(onSuccess).toHaveBeenCalledTimes(1)
193+
})
145194
})
146195
})

0 commit comments

Comments
 (0)