Repository: tannerlinsley/react-query
Pull request #1003 (2020-09-12 15:08:35)

test: add previous data test

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



Patch:
diff --git a/src/core/config.ts b/src/core/config.ts
index e87514462..215b1abe1 100644
--- a/src/core/config.ts
+++ b/src/core/config.ts
@@ -66,6 +66,14 @@ export const DEFAULT_CONFIG: ReactQueryConfig = {
   },
 }
 
+export function getDefaultReactQueryConfig() {
+  return {
+    shared: { ...DEFAULT_CONFIG.shared },
+    queries: { ...DEFAULT_CONFIG.queries },
+    mutations: { ...DEFAULT_CONFIG.mutations },
+  }
+}
+
 export function mergeReactQueryConfigs(
   a: ReactQueryConfig,
   b: ReactQueryConfig
diff --git a/src/core/index.ts b/src/core/index.ts
index a4636e68f..5c5b24081 100644
--- a/src/core/index.ts
+++ b/src/core/index.ts
@@ -1,3 +1,4 @@
+export { getDefaultReactQueryConfig } from './config'
 export { queryCache, queryCaches, makeQueryCache } from './queryCache'
 export { setFocusHandler } from './setFocusHandler'
 export { setOnlineHandler } from './setOnlineHandler'
diff --git a/src/core/query.ts b/src/core/query.ts
index 64da77d5d..96f66cdb9 100644
--- a/src/core/query.ts
+++ b/src/core/query.ts
@@ -32,16 +32,16 @@ export interface QueryState<TResult, TError> {
   data?: TResult
   error: TError | null
   failureCount: number
-  fetchedCount: number
   isError: boolean
-  isFetched: boolean
   isFetching: boolean
   isFetchingMore: IsFetchingMoreValue
   isIdle: boolean
+  isInitialData: boolean
   isLoading: boolean
   isSuccess: boolean
   status: QueryStatus
   throwInErrorBoundary?: boolean
+  updateCount: number
   updatedAt: number
 }
 
@@ -593,15 +593,15 @@ function getDefaultState<TResult, TError>(
 
   return {
     ...getStatusProps(initialStatus),
+    canFetchMore: hasMorePages(config, initialData),
+    data: initialData,
     error: null,
-    isFetched: Boolean(config.initialFetched),
+    failureCount: 0,
     isFetching: initialStatus === QueryStatus.Loading,
     isFetchingMore: false,
-    failureCount: 0,
-    fetchedCount: config.initialFetched ? 1 : 0,
-    data: initialData,
+    isInitialData: true,
+    updateCount: 0,
     updatedAt: Date.now(),
-    canFetchMore: hasMorePages(config, initialData),
   }
 }
 
@@ -631,27 +631,26 @@ export function queryReducer<TResult, TError>(
       return {
         ...state,
         ...getStatusProps(QueryStatus.Success),
+        canFetchMore: action.canFetchMore,
         data: action.data,
         error: null,
-        fetchedCount: state.fetchedCount + 1,
-        isFetched: true,
+        failureCount: 0,
         isFetching: false,
         isFetchingMore: false,
-        canFetchMore: action.canFetchMore,
+        isInitialData: false,
+        updateCount: state.updateCount + 1,
         updatedAt: action.updatedAt ?? Date.now(),
-        failureCount: 0,
       }
     case ActionType.Error:
       return {
         ...state,
         ...getStatusProps(QueryStatus.Error),
         error: action.error,
-        fetchedCount: state.fetchedCount + 1,
-        isFetched: true,
+        failureCount: state.failureCount + 1,
         isFetching: false,
         isFetchingMore: false,
-        failureCount: state.failureCount + 1,
         throwInErrorBoundary: true,
+        updateCount: state.updateCount + 1,
       }
     default:
       return state
diff --git a/src/core/queryCache.ts b/src/core/queryCache.ts
index ed932800b..c4eb38ab6 100644
--- a/src/core/queryCache.ts
+++ b/src/core/queryCache.ts
@@ -1,7 +1,6 @@
 import {
   Updater,
   deepIncludes,
-  functionalUpdate,
   getQueryArgs,
   isDocumentVisible,
   isPlainObject,
@@ -336,19 +335,7 @@ export class QueryCache {
     updater: Updater<TResult | undefined, TResult>,
     config?: QueryConfig<TResult, TError>
   ) {
-    const resolvedConfig = this.getResolvedQueryConfig(queryKey, config)
-    const query = this.getQueryByHash<TResult, TError>(resolvedConfig.queryHash)
-
-    if (query) {
-      query.setData(updater)
-      return
-    }
-
-    this.createQuery({
-      initialFetched: true,
-      initialData: functionalUpdate(updater, undefined),
-      ...resolvedConfig,
-    })
+    this.buildQuery(queryKey, config).setData(updater)
   }
 }
 
diff --git a/src/core/queryObserver.ts b/src/core/queryObserver.ts
index 0b7cc76f9..13c1f109d 100644
--- a/src/core/queryObserver.ts
+++ b/src/core/queryObserver.ts
@@ -18,13 +18,13 @@ export class QueryObserver<TResult, TError> {
   private currentResult!: QueryResult<TResult, TError>
   private previousQueryResult?: QueryResult<TResult, TError>
   private listener?: UpdateListener<TResult, TError>
-  private initialFetchedCount: number
+  private initialUpdateCount: number
   private staleTimeoutId?: number
   private refetchIntervalId?: number
 
   constructor(config: ResolvedQueryConfig<TResult, TError>) {
     this.config = config
-    this.initialFetchedCount = 0
+    this.initialUpdateCount = 0
 
     // Bind exposed methods
     this.clear = this.clear.bind(this)
@@ -161,6 +161,7 @@ export class QueryObserver<TResult, TError> {
       if (!this.currentResult.isStale) {
         this.currentResult = { ...this.currentResult, isStale: true }
         this.notify()
+        this.config.queryCache.notifyGlobalListeners(this.currentQuery)
       }
     }, timeout)
   }
@@ -229,7 +230,7 @@ export class QueryObserver<TResult, TError> {
 
     // When the query has not been fetched yet and this is the initial render,
     // determine the staleness based on the initialStale or existence of initial data.
-    if (!currentResult && !state.isFetched) {
+    if (!currentResult && state.isInitialData) {
       if (typeof config.initialStale === 'function') {
         isStale = config.initialStale()
       } else if (typeof config.initialStale === 'boolean') {
@@ -249,10 +250,11 @@ export class QueryObserver<TResult, TError> {
       error: state.error,
       failureCount: state.failureCount,
       fetchMore: this.fetchMore,
-      isFetched: state.isFetched,
-      isFetchedAfterMount: state.fetchedCount > this.initialFetchedCount,
+      isFetched: state.updateCount > 0,
+      isFetchedAfterMount: state.updateCount > this.initialUpdateCount,
       isFetching: state.isFetching,
       isFetchingMore: state.isFetchingMore,
+      isInitialData: state.isInitialData,
       isPreviousData,
       isStale,
       refetch: this.refetch,
@@ -284,7 +286,7 @@ export class QueryObserver<TResult, TError> {
 
     this.previousQueryResult = this.currentResult
     this.currentQuery = query
-    this.initialFetchedCount = query.state.fetchedCount
+    this.initialUpdateCount = query.state.updateCount
     this.updateResult()
 
     if (this.listener) {
diff --git a/src/core/types.ts b/src/core/types.ts
index eff7d0fd9..bc30aa9ec 100644
--- a/src/core/types.ts
+++ b/src/core/types.ts
@@ -49,7 +49,6 @@ export interface BaseQueryConfig<TResult, TError = unknown, TData = TResult> {
   queryKeySerializerFn?: QueryKeySerializerFunction
   queryFnParamsFilter?: (args: ArrayQueryKey) => ArrayQueryKey
   initialData?: TResult | InitialDataFunction<TResult>
-  initialFetched?: boolean
   infinite?: true
   /**
    * Set this to `false` to disable structural sharing between query results.
@@ -196,10 +195,11 @@ export interface QueryResultBase<TResult, TError = unknown> {
   isFetching: boolean
   isFetchingMore?: IsFetchingMoreValue
   isIdle: boolean
+  isInitialData: boolean
   isLoading: boolean
+  isPreviousData: boolean
   isStale: boolean
   isSuccess: boolean
-  isPreviousData: boolean
   refetch: (options?: RefetchOptions) => Promise<TResult | undefined>
   status: QueryStatus
   updatedAt: number


Test patch:
diff --git a/src/core/tests/queryCache.test.tsx b/src/core/tests/queryCache.test.tsx
index 4cb3d5018..f2762154e 100644
--- a/src/core/tests/queryCache.test.tsx
+++ b/src/core/tests/queryCache.test.tsx
@@ -544,7 +544,7 @@ describe('queryCache', () => {
       expect(query.state).toMatchObject({
         data: 'data',
         isLoading: false,
-        isFetched: true,
+        updateCount: 1,
       })
     })
 
@@ -578,7 +578,7 @@ describe('queryCache', () => {
       expect(query.state).toMatchObject({
         data: undefined,
         isLoading: false,
-        isFetched: true,
+        updateCount: 1,
       })
     })
 
diff --git a/src/react/tests/useInfiniteQuery.test.tsx b/src/react/tests/useInfiniteQuery.test.tsx
index fdf5a04b8..e5155c101 100644
--- a/src/react/tests/useInfiniteQuery.test.tsx
+++ b/src/react/tests/useInfiniteQuery.test.tsx
@@ -72,6 +72,7 @@ describe('useInfiniteQuery', () => {
       isFetching: true,
       isFetchingMore: false,
       isIdle: false,
+      isInitialData: true,
       isLoading: true,
       isPreviousData: false,
       isStale: true,
@@ -100,6 +101,7 @@ describe('useInfiniteQuery', () => {
       isFetchedAfterMount: true,
       isFetching: false,
       isIdle: false,
+      isInitialData: false,
       isLoading: false,
       isPreviousData: false,
       isStale: true,
diff --git a/src/react/tests/usePaginatedQuery.test.tsx b/src/react/tests/usePaginatedQuery.test.tsx
index c12f11637..09aa22a08 100644
--- a/src/react/tests/usePaginatedQuery.test.tsx
+++ b/src/react/tests/usePaginatedQuery.test.tsx
@@ -42,6 +42,7 @@ describe('usePaginatedQuery', () => {
       isFetching: true,
       isFetchingMore: false,
       isIdle: false,
+      isInitialData: true,
       isLoading: true,
       isPreviousData: false,
       isStale: true,
@@ -66,6 +67,7 @@ describe('usePaginatedQuery', () => {
       isFetching: false,
       isFetchingMore: false,
       isIdle: false,
+      isInitialData: false,
       isLoading: false,
       isPreviousData: false,
       isStale: true,
diff --git a/src/react/tests/useQuery.test.tsx b/src/react/tests/useQuery.test.tsx
index 684ac832b..15dcc1d2a 100644
--- a/src/react/tests/useQuery.test.tsx
+++ b/src/react/tests/useQuery.test.tsx
@@ -134,6 +134,7 @@ describe('useQuery', () => {
       isFetching: true,
       isFetchingMore: false,
       isIdle: false,
+      isInitialData: true,
       isLoading: true,
       isPreviousData: false,
       isStale: true,
@@ -156,6 +157,7 @@ describe('useQuery', () => {
       isFetching: false,
       isFetchingMore: false,
       isIdle: false,
+      isInitialData: false,
       isLoading: false,
       isPreviousData: false,
       isStale: true,
@@ -208,6 +210,7 @@ describe('useQuery', () => {
       isFetching: true,
       isFetchingMore: false,
       isIdle: false,
+      isInitialData: true,
       isLoading: true,
       isPreviousData: false,
       isStale: true,
@@ -230,6 +233,7 @@ describe('useQuery', () => {
       isFetching: true,
       isFetchingMore: false,
       isIdle: false,
+      isInitialData: true,
       isLoading: true,
       isPreviousData: false,
       isStale: true,
@@ -252,6 +256,7 @@ describe('useQuery', () => {
       isFetching: false,
       isFetchingMore: false,
       isIdle: false,
+      isInitialData: true,
       isLoading: false,
       isPreviousData: false,
       isStale: true,
@@ -667,6 +672,86 @@ describe('useQuery', () => {
     })
   })
 
+  it('should keep the previous data on disabled query when keepPreviousData is set and switching query key multiple times', async () => {
+    const key = queryKey()
+    const states: QueryResult<number>[] = []
+
+    queryCache.setQueryData([key, 10], 10)
+
+    await sleep(10)
+
+    function Page() {
+      const [count, setCount] = React.useState(10)
+
+      const state = useQuery(
+        [key, count],
+        async () => {
+          await sleep(10)
+          return count
+        },
+        { enabled: false, keepPreviousData: true }
+      )
+
+      states.push(state)
+
+      const { refetch } = state
+
+      React.useEffect(() => {
+        setTimeout(() => {
+          setCount(11)
+        }, 20)
+        setTimeout(() => {
+          setCount(12)
+        }, 30)
+        setTimeout(() => {
+          refetch()
+        }, 40)
+      }, [refetch])
+
+      return null
+    }
+
+    render(<Page />)
+
+    await waitFor(() => expect(states.length).toBe(5))
+
+    // Disabled query
+    expect(states[0]).toMatchObject({
+      data: 10,
+      isFetching: false,
+      isSuccess: true,
+      isPreviousData: false,
+    })
+    // Switched query key
+    expect(states[1]).toMatchObject({
+      data: 10,
+      isFetching: false,
+      isSuccess: true,
+      isPreviousData: true,
+    })
+    // Switched query key
+    expect(states[2]).toMatchObject({
+      data: 10,
+      isFetching: false,
+      isSuccess: true,
+      isPreviousData: true,
+    })
+    // Refetch
+    expect(states[3]).toMatchObject({
+      data: 10,
+      isFetching: true,
+      isSuccess: true,
+      isPreviousData: true,
+    })
+    // Refetch done
+    expect(states[4]).toMatchObject({
+      data: 12,
+      isFetching: false,
+      isSuccess: true,
+      isPreviousData: false,
+    })
+  })
+
   it('should use the correct query function when components use different configurations', async () => {
     const key = queryKey()
     const states: QueryResult<number>[] = []
@@ -829,6 +914,28 @@ describe('useQuery', () => {
     )
   })
 
+  it('should notify query cache when a query becomes stale', async () => {
+    const key = queryKey()
+    const states: QueryResult<string>[] = []
+    const fn = jest.fn()
+
+    const unsubscribe = queryCache.subscribe(fn)
+
+    function Page() {
+      const state = useQuery(key, () => 'test', {
+        staleTime: 10,
+      })
+      states.push(state)
+      return null
+    }
+
+    render(<Page />)
+
+    await waitForMs(20)
+    unsubscribe()
+    expect(fn).toHaveBeenCalledTimes(3)
+  })
+
   it('should not re-render when a query status changes and notifyOnStatusChange is false', async () => {
     const key = queryKey()
     const states: QueryResult<string>[] = []