Skip to content
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
5 changes: 5 additions & 0 deletions docs/src/pages/docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const {
retryDelay,
staleTime,
cacheTime,
keepPreviousData,
refetchOnWindowFocus,
refetchInterval,
refetchIntervalInBackground,
Expand Down Expand Up @@ -112,6 +113,10 @@ const queryInfo = useQuery({
- Optional
- If set, this will mark any `initialData` provided as stale and will likely cause it to be refetched on mount
- If a function is passed, it will be called only when appropriate to resolve the `initialStale` value. This can be useful if your `initialStale` value is costly to calculate.
- `keepPreviousData: Boolean`
- Optional
- Defaults to `false`
- If set, any previous `data` will be kept when fetching new data because the query key changed.
- `refetchOnMount: Boolean`
- Optional
- Defaults to `true`
Expand Down
12 changes: 1 addition & 11 deletions src/core/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { stableStringify, identity } from './utils'
import { stableStringify } from './utils'
import {
ArrayQueryKey,
QueryKey,
Expand Down Expand Up @@ -30,9 +30,6 @@ export const defaultQueryKeySerializerFn: QueryKeySerializerFunction = (
}

export const DEFAULT_CONFIG: ReactQueryConfig = {
shared: {
suspense: false,
},
queries: {
queryKeySerializerFn: defaultQueryKeySerializerFn,
enabled: true,
Expand All @@ -41,14 +38,7 @@ export const DEFAULT_CONFIG: ReactQueryConfig = {
staleTime: 0,
cacheTime: 5 * 60 * 1000,
refetchOnWindowFocus: true,
refetchInterval: false,
queryFnParamsFilter: identity,
refetchOnMount: true,
useErrorBoundary: false,
},
mutations: {
throwOnError: false,
useErrorBoundary: false,
},
}

Expand Down
113 changes: 81 additions & 32 deletions src/core/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
Updater,
replaceEqualDeep,
} from './utils'
import { QueryInstance, OnStateUpdateFunction } from './queryInstance'
import {
ArrayQueryKey,
InfiniteQueryConfig,
Expand All @@ -19,7 +18,8 @@ import {
QueryFunction,
QueryStatus,
} from './types'
import { QueryCache } from './queryCache'
import type { QueryCache } from './queryCache'
import { QueryObserver, UpdateListener } from './queryObserver'

// TYPES

Expand All @@ -39,7 +39,7 @@ export interface QueryState<TResult, TError> {
isError: boolean
isFetched: boolean
isFetching: boolean
isFetchingMore?: IsFetchingMoreValue
isFetchingMore: IsFetchingMoreValue
isIdle: boolean
isLoading: boolean
isStale: boolean
Expand Down Expand Up @@ -111,7 +111,7 @@ export class Query<TResult, TError> {
queryKey: ArrayQueryKey
queryHash: string
config: QueryConfig<TResult, TError>
instances: QueryInstance<TResult, TError>[]
observers: QueryObserver<TResult, TError>[]
state: QueryState<TResult, TError>
shouldContinueRetryOnFocus?: boolean
promise?: Promise<TResult | undefined>
Expand All @@ -131,17 +131,14 @@ export class Query<TResult, TError> {
this.queryKey = init.queryKey
this.queryHash = init.queryHash
this.notifyGlobalListeners = init.notifyGlobalListeners
this.instances = []
this.observers = []
this.state = getDefaultState(init.config)

if (init.config.infinite) {
const infiniteConfig = init.config as InfiniteQueryConfig<TResult, TError>
const infiniteData = (this.state.data as unknown) as TResult[] | undefined

if (
typeof infiniteData !== 'undefined' &&
typeof this.state.canFetchMore === 'undefined'
) {
if (typeof infiniteData !== 'undefined') {
this.fetchMoreVariable = infiniteConfig.getFetchMore(
infiniteData[infiniteData.length - 1],
infiniteData
Expand All @@ -154,11 +151,28 @@ export class Query<TResult, TError> {
this.pageVariables = [[...this.queryKey]]
}
}

// If the query started with data, schedule
// a stale timeout
if (!isServer && this.state.data) {
this.scheduleStaleTimeout()

// Simulate a query healing process
this.heal()

// Schedule for garbage collection in case
// nothing subscribes to this query
this.scheduleGarbageCollection()
}
}

updateConfig(config: QueryConfig<TResult, TError>): void {
this.config = config
}

private dispatch(action: Action<TResult, TError>): void {
this.state = queryReducer(this.state, action)
this.instances.forEach(d => d.onStateUpdate(this.state, action))
this.observers.forEach(d => d.onQueryUpdate(this.state, action))
this.notifyGlobalListeners(this)
}

Expand All @@ -169,11 +183,7 @@ export class Query<TResult, TError> {

this.clearStaleTimeout()

if (this.state.isStale) {
return
}

if (this.config.staleTime === Infinity) {
if (this.state.isStale || this.config.staleTime === Infinity) {
return
}

Expand All @@ -185,10 +195,6 @@ export class Query<TResult, TError> {
invalidate(): void {
this.clearStaleTimeout()

if (!this.queryCache.queries[this.queryHash]) {
return
}

if (this.state.isStale) {
return
}
Expand All @@ -197,12 +203,12 @@ export class Query<TResult, TError> {
}

scheduleGarbageCollection(): void {
this.clearCacheTimeout()

if (!this.queryCache.queries[this.queryHash]) {
if (isServer) {
return
}

this.clearCacheTimeout()

if (this.config.cacheTime === Infinity) {
return
}
Expand Down Expand Up @@ -244,9 +250,9 @@ export class Query<TResult, TError> {
delete this.promise
}

clearIntervals(): void {
this.instances.forEach(instance => {
instance.clearInterval()
private clearTimersObservers(): void {
this.observers.forEach(observer => {
observer.clearRefetchInterval()
})
}

Expand Down Expand Up @@ -310,19 +316,57 @@ export class Query<TResult, TError> {
this.clearStaleTimeout()
this.clearCacheTimeout()
this.clearRetryTimeout()
this.clearIntervals()
this.clearTimersObservers()
this.cancel()
delete this.queryCache.queries[this.queryHash]
this.notifyGlobalListeners(this)
}

isEnabled(): boolean {
return this.observers.some(observer => observer.config.enabled)
}

shouldRefetchOnWindowFocus(): boolean {
return (
this.isEnabled() &&
this.state.isStale &&
this.observers.some(observer => observer.config.refetchOnWindowFocus)
)
}

subscribe(
onStateUpdate?: OnStateUpdateFunction<TResult, TError>
): QueryInstance<TResult, TError> {
const instance = new QueryInstance(this, onStateUpdate)
this.instances.push(instance)
listener?: UpdateListener<TResult, TError>
): QueryObserver<TResult, TError> {
const observer = new QueryObserver<TResult, TError>({
queryCache: this.queryCache,
queryKey: this.queryKey,
...this.config,
})

observer.subscribe(listener)

return observer
}

subscribeObserver(observer: QueryObserver<TResult, TError>): void {
this.observers.push(observer)
this.heal()
return instance
}

unsubscribeObserver(
observer: QueryObserver<TResult, TError>,
preventGC?: boolean
): void {
this.observers = this.observers.filter(x => x !== observer)

if (!this.observers.length) {
this.cancel()

if (!preventGC) {
// Schedule garbage collection
this.scheduleGarbageCollection()
}
}
}

// Set up the core fetcher function
Expand All @@ -332,7 +376,11 @@ export class Query<TResult, TError> {
): Promise<TResult> {
try {
// Perform the query
const promiseOrValue = fn(...this.config.queryFnParamsFilter!(args))
const filter = this.config.queryFnParamsFilter
const params = filter ? filter(args) : args

// Perform the query
const promiseOrValue = fn(...params)

this.cancelPromises = () => (promiseOrValue as any)?.cancel?.()

Expand Down Expand Up @@ -584,6 +632,7 @@ function getDefaultState<TResult, TError>(
error: null,
isFetched: false,
isFetching: initialStatus === QueryStatus.Loading,
isFetchingMore: false,
failureCount: 0,
isStale,
data: initialData,
Expand Down
40 changes: 15 additions & 25 deletions src/core/queryCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,15 @@ export class QueryCache {
return this.configRef.current
}

getDefaultedConfig<TResult, TError>(config?: QueryConfig<TResult, TError>) {
return {
...this.configRef.current.shared!,
...this.configRef.current.queries!,
queryCache: this,
...config,
} as QueryConfig<TResult, TError>
}

subscribe(listener: QueryCacheListener): () => void {
this.globalListeners.push(listener)
return () => {
Expand Down Expand Up @@ -195,11 +204,8 @@ export class QueryCache {
try {
await Promise.all(
this.getQueries(predicate, options).map(query => {
if (query.instances.length) {
if (
refetchActive &&
query.instances.some(instance => instance.config.enabled)
) {
if (query.observers.length) {
if (refetchActive && query.isEnabled()) {
return query.fetch()
}
} else {
Expand All @@ -226,21 +232,17 @@ export class QueryCache {

buildQuery<TResult, TError = unknown>(
userQueryKey: QueryKey,
queryConfig: QueryConfig<TResult, TError> = {}
queryConfig?: QueryConfig<TResult, TError>
): Query<TResult, TError> {
const config = {
...this.configRef.current.shared!,
...this.configRef.current.queries!,
...queryConfig,
} as QueryConfig<TResult, TError>
const config = this.getDefaultedConfig(queryConfig)

const [queryHash, queryKey] = config.queryKeySerializerFn!(userQueryKey)

let query

if (this.queries[queryHash]) {
query = this.queries[queryHash] as Query<TResult, TError>
query.config = config
query.updateConfig(config)
}

if (!query) {
Expand All @@ -254,18 +256,6 @@ export class QueryCache {
},
})

// If the query started with data, schedule
// a stale timeout
if (!isServer && query.state.data) {
query.scheduleStaleTimeout()

// Simulate a query healing process
query.heal()
// Schedule for garbage collection in case
// nothing subscribes to this query
query.scheduleGarbageCollection()
}

if (!this.config.frozen) {
this.queries[queryHash] = query

Expand Down Expand Up @@ -386,7 +376,7 @@ export class QueryCache {
setQueryData<TResult, TError = unknown>(
queryKey: QueryKey,
updater: Updater<TResult | undefined, TResult>,
config: QueryConfig<TResult, TError> = {}
config?: QueryConfig<TResult, TError>
) {
let query = this.getQuery<TResult, TError>(queryKey)

Expand Down
Loading