Repository: tannerlinsley/react-query
Pull request #964 (2020-09-04 06:54:00)

fix: use hook config on refocus or reconnect
On window refocus or reconnect, trigger the first observer which indicates it should be refetched.

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/cnh0wcb8q](https://vercel.com/tannerlinsley/react-query/cnh0wcb8q)  
✅ Preview: *In Progress*



Patch:
diff --git a/src/core/query.ts b/src/core/query.ts
index 00c0bc202..1790ba06d 100644
--- a/src/core/query.ts
+++ b/src/core/query.ts
@@ -231,33 +231,22 @@ export class Query<TResult, TError> {
     )
   }
 
-  onWindowFocus(): void {
-    if (
-      this.observers.some(
-        observer =>
-          observer.isStale() &&
-          observer.config.enabled &&
-          observer.config.refetchOnWindowFocus
-      )
-    ) {
-      this.fetch().catch(noop)
-    }
-
-    this.continue()
-  }
+  onInteraction(type: 'focus' | 'online'): void {
+    // Execute the first observer which is enabled,
+    // stale and wants to refetch on this interaction.
+    const observer = this.observers.find(
+      observer =>
+        observer.isStale() &&
+        observer.config.enabled &&
+        ((observer.config.refetchOnWindowFocus && type === 'focus') ||
+          (observer.config.refetchOnReconnect && type === 'online'))
+    )
 
-  onOnline(): void {
-    if (
-      this.observers.some(
-        observer =>
-          observer.isStale() &&
-          observer.config.enabled &&
-          observer.config.refetchOnReconnect
-      )
-    ) {
-      this.fetch().catch(noop)
+    if (observer) {
+      observer.fetch().catch(noop)
     }
 
+    // Continue any paused fetch
     this.continue()
   }
 
diff --git a/src/core/queryCache.ts b/src/core/queryCache.ts
index 17fd17cc3..bc5a1cdbd 100644
--- a/src/core/queryCache.ts
+++ b/src/core/queryCache.ts
@@ -354,15 +354,11 @@ export function makeQueryCache(config?: QueryCacheConfig) {
   return new QueryCache(config)
 }
 
-export function onVisibilityOrOnlineChange(isOnlineChange: boolean) {
+export function onVisibilityOrOnlineChange(type: 'focus' | 'online') {
   if (isDocumentVisible() && isOnline()) {
     queryCaches.forEach(queryCache => {
       queryCache.getQueries().forEach(query => {
-        if (isOnlineChange) {
-          query.onOnline()
-        } else {
-          query.onWindowFocus()
-        }
+        query.onInteraction(type)
       })
     })
   }
diff --git a/src/core/setFocusHandler.ts b/src/core/setFocusHandler.ts
index 2fdb02795..4b6975415 100644
--- a/src/core/setFocusHandler.ts
+++ b/src/core/setFocusHandler.ts
@@ -2,7 +2,7 @@ import { createSetHandler, isServer } from './utils'
 import { onVisibilityOrOnlineChange } from './queryCache'
 
 export const setFocusHandler = createSetHandler(() =>
-  onVisibilityOrOnlineChange(false)
+  onVisibilityOrOnlineChange('focus')
 )
 
 setFocusHandler(handleFocus => {
diff --git a/src/core/setOnlineHandler.ts b/src/core/setOnlineHandler.ts
index 853bf08cb..a7f6303cf 100644
--- a/src/core/setOnlineHandler.ts
+++ b/src/core/setOnlineHandler.ts
@@ -2,7 +2,7 @@ import { createSetHandler, isServer } from './utils'
 import { onVisibilityOrOnlineChange } from './queryCache'
 
 export const setOnlineHandler = createSetHandler(() =>
-  onVisibilityOrOnlineChange(true)
+  onVisibilityOrOnlineChange('online')
 )
 
 setOnlineHandler(handleOnline => {


Test patch:
diff --git a/src/react/tests/useQuery.test.tsx b/src/react/tests/useQuery.test.tsx
index 8e1736faa..059729dc2 100644
--- a/src/react/tests/useQuery.test.tsx
+++ b/src/react/tests/useQuery.test.tsx
@@ -644,7 +644,7 @@ describe('useQuery', () => {
 
     await queryCache.prefetchQuery(key, () => 'prefetch')
 
-    await sleep(40)
+    await sleep(20)
 
     function FirstComponent() {
       const state = useQuery(key, () => 'one', {
@@ -656,7 +656,7 @@ describe('useQuery', () => {
 
     function SecondComponent() {
       const state = useQuery(key, () => 'two', {
-        staleTime: 20,
+        staleTime: 10,
       })
       states2.push(state)
       return null
@@ -673,50 +673,55 @@ describe('useQuery', () => {
 
     render(<Page />)
 
-    await waitFor(() => expect(states1.length).toBe(4))
-    await waitFor(() => expect(states2.length).toBe(4))
-
-    // First render
-    expect(states1[0]).toMatchObject({
-      data: 'prefetch',
-      isStale: false,
-    })
-    // Second useQuery started fetching
-    expect(states1[1]).toMatchObject({
-      data: 'prefetch',
-      isStale: false,
-    })
-    // Second useQuery data came in
-    expect(states1[2]).toMatchObject({
-      data: 'two',
-      isStale: false,
-    })
-    // Data became stale after 100ms
-    expect(states1[3]).toMatchObject({
-      data: 'two',
-      isStale: true,
-    })
+    await waitFor(() =>
+      expect(states1).toMatchObject([
+        // First render
+        {
+          data: 'prefetch',
+          isStale: false,
+        },
+        // Second useQuery started fetching
+        {
+          data: 'prefetch',
+          isStale: false,
+        },
+        // Second useQuery data came in
+        {
+          data: 'two',
+          isStale: false,
+        },
+        // Data became stale after 100ms
+        {
+          data: 'two',
+          isStale: true,
+        },
+      ])
+    )
 
-    // First render, data is stale
-    expect(states2[0]).toMatchObject({
-      data: 'prefetch',
-      isStale: true,
-    })
-    // Second useQuery started fetching
-    expect(states2[1]).toMatchObject({
-      data: 'prefetch',
-      isStale: true,
-    })
-    // Second useQuery data came in
-    expect(states2[2]).toMatchObject({
-      data: 'two',
-      isStale: false,
-    })
-    // Data became stale after 5ms
-    expect(states2[3]).toMatchObject({
-      data: 'two',
-      isStale: true,
-    })
+    await waitFor(() =>
+      expect(states2).toMatchObject([
+        // First render, data is stale
+        {
+          data: 'prefetch',
+          isStale: true,
+        },
+        // Second useQuery started fetching
+        {
+          data: 'prefetch',
+          isStale: true,
+        },
+        // Second useQuery data came in
+        {
+          data: 'two',
+          isStale: false,
+        },
+        // Data became stale after 5ms
+        {
+          data: 'two',
+          isStale: true,
+        },
+      ])
+    )
   })
 
   it('should re-render when a query becomes stale', async () => {
@@ -1220,6 +1225,62 @@ describe('useQuery', () => {
     consoleMock.mockRestore()
   })
 
+  it('should refetch after focus regain', async () => {
+    const key = queryKey()
+    const states: QueryResult<string>[] = []
+    const consoleMock = mockConsoleError()
+
+    // make page unfocused
+    const originalVisibilityState = document.visibilityState
+    mockVisibilityState('hidden')
+
+    // set data in cache to check if the hook query fn is actually called
+    queryCache.setQueryData(key, 'prefetched')
+
+    function Page() {
+      const state = useQuery(key, () => 'data')
+      states.push(state)
+      return null
+    }
+
+    render(<Page />)
+
+    await waitFor(() => expect(states.length).toBe(2))
+
+    act(() => {
+      // reset visibilityState to original value
+      mockVisibilityState(originalVisibilityState)
+      window.dispatchEvent(new FocusEvent('focus'))
+    })
+
+    await waitFor(() => expect(states.length).toBe(4))
+
+    expect(states).toMatchObject([
+      {
+        data: 'prefetched',
+        isFetching: false,
+        isStale: false,
+      },
+      {
+        data: 'prefetched',
+        isFetching: false,
+        isStale: true,
+      },
+      {
+        data: 'prefetched',
+        isFetching: true,
+        isStale: true,
+      },
+      {
+        data: 'data',
+        isFetching: false,
+        isStale: true,
+      },
+    ])
+
+    consoleMock.mockRestore()
+  })
+
   // See https://github.com/tannerlinsley/react-query/issues/195
   it('should refetch if stale after a prefetch', async () => {
     const key = queryKey()