Repository: tannerlinsley/react-query
Pull request #1442 (2020-12-15 18:47:31)

fix: allow queries to be fetched with zero cache time and do not hydrate it
Fixes for https://github.com/tannerlinsley/react-query/issues/1432 .

1. Prevents queries from getting cancelled in flight when a low cache time is set.
2. Do not hydrate cache time because environments can have different caching requirements.

vercel[bot]: This pull request is being automatically deployed with Vercel ([learn more](https://vercel.link/github-learn-more)).  
To see the status of your deployment, click below or on the icon next to each commit.

🔍 Inspect: [https://vercel.com/tannerlinsley/react-query/gixxjbysv](https://vercel.com/tannerlinsley/react-query/gixxjbysv)  
✅ Preview: [https://react-query-git-feature-fix-cache-times.tannerlinsley.vercel.app](https://react-query-git-feature-fix-cache-times.tannerlinsley.vercel.app)



Patch:
diff --git a/src/core/query.ts b/src/core/query.ts
index fec4bf4f6..c31a8c69a 100644
--- a/src/core/query.ts
+++ b/src/core/query.ts
@@ -168,9 +168,7 @@ export class Query<TData = unknown, TError = unknown, TQueryFnData = TData> {
 
     if (isValidTimeout(this.cacheTime)) {
       this.gcTimeout = setTimeout(() => {
-        if (!this.observers.length) {
-          this.cache.remove(this)
-        }
+        this.optionalRemove()
       }, this.cacheTime)
     }
   }
@@ -180,6 +178,12 @@ export class Query<TData = unknown, TError = unknown, TQueryFnData = TData> {
     this.gcTimeout = undefined
   }
 
+  private optionalRemove() {
+    if (!this.observers.length && !this.state.isFetching) {
+      this.cache.remove(this)
+    }
+  }
+
   setData(
     updater: Updater<TData | undefined, TData>,
     options?: SetDataOptions
@@ -412,6 +416,12 @@ export class Query<TData = unknown, TError = unknown, TQueryFnData = TData> {
         // Propagate error
         throw error
       })
+      .finally(() => {
+        // Remove query after fetching if cache time is 0
+        if (this.cacheTime === 0) {
+          this.optionalRemove()
+        }
+      })
 
     return this.promise
   }
diff --git a/src/hydration/hydration.ts b/src/hydration/hydration.ts
index 9842ffda3..b0aa9bc9d 100644
--- a/src/hydration/hydration.ts
+++ b/src/hydration/hydration.ts
@@ -30,7 +30,6 @@ interface DehydratedMutation {
 }
 
 interface DehydratedQuery {
-  cacheTime: number
   queryHash: string
   queryKey: QueryKey
   state: QueryState
@@ -47,14 +46,6 @@ export type ShouldDehydrateMutationFunction = (mutation: Mutation) => boolean
 
 // FUNCTIONS
 
-function serializePositiveNumber(value: number): number {
-  return value === Infinity ? -1 : value
-}
-
-function deserializePositiveNumber(value: number): number {
-  return value === -1 ? Infinity : value
-}
-
 function dehydrateMutation(mutation: Mutation): DehydratedMutation {
   return {
     mutationKey: mutation.options.mutationKey,
@@ -68,7 +59,6 @@ function dehydrateMutation(mutation: Mutation): DehydratedMutation {
 // in the html-payload, but not consume it on the initial render.
 function dehydrateQuery(query: Query): DehydratedQuery {
   return {
-    cacheTime: serializePositiveNumber(query.cacheTime),
     state: query.state,
     queryKey: query.queryKey,
     queryHash: query.queryHash,
@@ -167,7 +157,6 @@ export function hydrate(
         ...options?.defaultOptions?.queries,
         queryKey: dehydratedQuery.queryKey,
         queryHash: dehydratedQuery.queryHash,
-        cacheTime: deserializePositiveNumber(dehydratedQuery.cacheTime),
       },
       dehydratedQuery.state
     )


Test patch:
diff --git a/src/core/tests/queryCache.test.tsx b/src/core/tests/queryCache.test.tsx
index 8fa80a6fd..ac06b4694 100644
--- a/src/core/tests/queryCache.test.tsx
+++ b/src/core/tests/queryCache.test.tsx
@@ -127,6 +127,42 @@ describe('queryCache', () => {
     expect(second).toBe(first)
   })
 
+  test('fetchQuery should be able to fetch when cache time is set to 0 and then be removed', async () => {
+    const testClient = new QueryClient({
+      defaultOptions: { queries: { cacheTime: 0 } },
+    })
+
+    const key1 = queryKey()
+
+    const result = await testClient.fetchQuery(key1, async () => {
+      await sleep(10)
+      return 1
+    })
+
+    const result2 = testClient.getQueryData(key1)
+
+    expect(result).toEqual(1)
+    expect(result2).toEqual(undefined)
+  })
+
+  test('fetchQuery should keep a query in cache if cache time is Infinity', async () => {
+    const testClient = new QueryClient({
+      defaultOptions: { queries: { cacheTime: Infinity } },
+    })
+
+    const key1 = queryKey()
+
+    const result = await testClient.fetchQuery(key1, async () => {
+      await sleep(10)
+      return 1
+    })
+
+    const result2 = testClient.getQueryData(key1)
+
+    expect(result).toEqual(1)
+    expect(result2).toEqual(1)
+  })
+
   test('fetchQuery should not force fetch', async () => {
     const key = queryKey()
 
diff --git a/src/hydration/tests/hydration.test.tsx b/src/hydration/tests/hydration.test.tsx
index 82ef5c088..3527e9ecb 100644
--- a/src/hydration/tests/hydration.test.tsx
+++ b/src/hydration/tests/hydration.test.tsx
@@ -62,7 +62,7 @@ describe('dehydration and rehydration', () => {
     hydrationClient.clear()
   })
 
-  test('should schedule garbage collection, measured from hydration', async () => {
+  test('should use the cache time from the client', async () => {
     const queryCache = new QueryCache()
     const queryClient = new QueryClient({ queryCache })
     await queryClient.prefetchQuery('string', () => fetchData('string'), {
@@ -80,28 +80,9 @@ describe('dehydration and rehydration', () => {
     const hydrationClient = new QueryClient({ queryCache: hydrationCache })
     hydrate(hydrationClient, parsed)
     expect(hydrationCache.find('string')?.state.data).toBe('string')
-    await sleep(10)
-    expect(hydrationCache.find('string')).toBeTruthy()
     await sleep(100)
-    expect(hydrationCache.find('string')).toBeFalsy()
-
-    queryClient.clear()
-    hydrationClient.clear()
-  })
+    expect(hydrationCache.find('string')).toBeTruthy()
 
-  test('should serialize the cacheTime correctly', async () => {
-    const queryCache = new QueryCache()
-    const queryClient = new QueryClient({ queryCache })
-    await queryClient.prefetchQuery('string', () => fetchData('string'), {
-      cacheTime: Infinity,
-    })
-    const dehydrated = dehydrate(queryClient)
-    const stringified = JSON.stringify(dehydrated)
-    const parsed = JSON.parse(stringified)
-    const hydrationCache = new QueryCache()
-    const hydrationClient = new QueryClient({ queryCache: hydrationCache })
-    hydrate(hydrationClient, parsed)
-    expect(hydrationCache.find('string')?.cacheTime).toBe(Infinity)
     queryClient.clear()
     hydrationClient.clear()
   })