Skip to content

fix: notify query cache on stale #1001

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/core/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { getDefaultReactQueryConfig } from './config'
export { queryCache, queryCaches, makeQueryCache } from './queryCache'
export { setFocusHandler } from './setFocusHandler'
export { setOnlineHandler } from './setOnlineHandler'
Expand Down
27 changes: 13 additions & 14 deletions src/core/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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),
}
}

Expand Down Expand Up @@ -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
Expand Down
15 changes: 1 addition & 14 deletions src/core/queryCache.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {
Updater,
deepIncludes,
functionalUpdate,
getQueryArgs,
isDocumentVisible,
isPlainObject,
Expand Down Expand Up @@ -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)
}
}

Expand Down
14 changes: 8 additions & 6 deletions src/core/queryObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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') {
Expand All @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions src/core/tests/queryCache.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ describe('queryCache', () => {
expect(query.state).toMatchObject({
data: 'data',
isLoading: false,
isFetched: true,
updateCount: 1,
})
})

Expand Down Expand Up @@ -578,7 +578,7 @@ describe('queryCache', () => {
expect(query.state).toMatchObject({
data: undefined,
isLoading: false,
isFetched: true,
updateCount: 1,
})
})

Expand Down
4 changes: 2 additions & 2 deletions src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/react/tests/useInfiniteQuery.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ describe('useInfiniteQuery', () => {
isFetching: true,
isFetchingMore: false,
isIdle: false,
isInitialData: true,
isLoading: true,
isPreviousData: false,
isStale: true,
Expand Down Expand Up @@ -100,6 +101,7 @@ describe('useInfiniteQuery', () => {
isFetchedAfterMount: true,
isFetching: false,
isIdle: false,
isInitialData: false,
isLoading: false,
isPreviousData: false,
isStale: true,
Expand Down
2 changes: 2 additions & 0 deletions src/react/tests/usePaginatedQuery.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ describe('usePaginatedQuery', () => {
isFetching: true,
isFetchingMore: false,
isIdle: false,
isInitialData: true,
isLoading: true,
isPreviousData: false,
isStale: true,
Expand All @@ -66,6 +67,7 @@ describe('usePaginatedQuery', () => {
isFetching: false,
isFetchingMore: false,
isIdle: false,
isInitialData: false,
isLoading: false,
isPreviousData: false,
isStale: true,
Expand Down
27 changes: 27 additions & 0 deletions src/react/tests/useQuery.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ describe('useQuery', () => {
isFetching: true,
isFetchingMore: false,
isIdle: false,
isInitialData: true,
isLoading: true,
isPreviousData: false,
isStale: true,
Expand All @@ -156,6 +157,7 @@ describe('useQuery', () => {
isFetching: false,
isFetchingMore: false,
isIdle: false,
isInitialData: false,
isLoading: false,
isPreviousData: false,
isStale: true,
Expand Down Expand Up @@ -208,6 +210,7 @@ describe('useQuery', () => {
isFetching: true,
isFetchingMore: false,
isIdle: false,
isInitialData: true,
isLoading: true,
isPreviousData: false,
isStale: true,
Expand All @@ -230,6 +233,7 @@ describe('useQuery', () => {
isFetching: true,
isFetchingMore: false,
isIdle: false,
isInitialData: true,
isLoading: true,
isPreviousData: false,
isStale: true,
Expand All @@ -252,6 +256,7 @@ describe('useQuery', () => {
isFetching: false,
isFetchingMore: false,
isIdle: false,
isInitialData: true,
isLoading: false,
isPreviousData: false,
isStale: true,
Expand Down Expand Up @@ -829,6 +834,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>[] = []
Expand Down