Skip to content

Commit

Permalink
refactor(types): make queryKey required on ObserverOptions (#6879)
Browse files Browse the repository at this point in the history
* refactor(types): make queryKey required on ObserverOptions

we cannot create an observer without a key, because an observer, per definition, watches a certain query by its key

* test: observers need a key

* chore: remove a branch that can't exist

we can't have an observer without a queryKey

* fix: when comparing with shallowEqualObjects, the currentResult must come first

because it doesn't detect length changes

* test: we get another event

* types: you can't set queryKey as default

* a test for undefined queryKey makes no sense

* fix: more type issues

* types: queryKey is also not required for defaultOptions in solid

* fix(core): shallowEqualObjects should return false for objects with different lengths

as we use it for options, and the length of options can change
  • Loading branch information
TkDodo authored Feb 17, 2024
1 parent 7595412 commit 61f4d74
Show file tree
Hide file tree
Showing 22 changed files with 127 additions and 141 deletions.
1 change: 0 additions & 1 deletion docs/reference/QueryCache.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ const query = queryCache.find(queryKey)

**Options**

- `queryKey?: QueryKey`: [Query Keys](./framework/react/guides/query-keys)
- `filters?: QueryFilters`: [Query Filters](./framework/react/guides/filters#query-filters)

**Returns**
Expand Down
46 changes: 21 additions & 25 deletions packages/angular-query-experimental/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import type {
QueryKey,
QueryObserverOptions,
QueryObserverResult,
WithRequired,
} from '@tanstack/query-core'
import type { MapToSignals } from './signal-proxy'

Expand All @@ -22,9 +21,12 @@ export interface CreateBaseQueryOptions<
TData = TQueryFnData,
TQueryData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
> extends WithRequired<
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>,
'queryKey'
> extends QueryObserverOptions<
TQueryFnData,
TError,
TData,
TQueryData,
TQueryKey
> {}

export interface CreateQueryOptions<
Expand All @@ -33,15 +35,12 @@ export interface CreateQueryOptions<
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
> extends Omit<
WithRequired<
CreateBaseQueryOptions<
TQueryFnData,
TError,
TData,
TQueryFnData,
TQueryKey
>,
'queryKey'
CreateBaseQueryOptions<
TQueryFnData,
TError,
TData,
TQueryFnData,
TQueryKey
>,
'suspense'
> {}
Expand Down Expand Up @@ -83,19 +82,16 @@ export interface CreateInfiniteQueryOptions<
TQueryData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
TPageParam = unknown,
> extends WithRequired<
Omit<
InfiniteQueryObserverOptions<
TQueryFnData,
TError,
TData,
TQueryData,
TQueryKey,
TPageParam
>,
'suspense'
> extends Omit<
InfiniteQueryObserverOptions<
TQueryFnData,
TError,
TData,
TQueryData,
TQueryKey,
TPageParam
>,
'queryKey'
'suspense'
> {}

export type CreateBaseQueryResult<
Expand Down
2 changes: 1 addition & 1 deletion packages/query-core/src/infiniteQueryObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export class InfiniteQueryObserver<
}

setOptions(
options?: InfiniteQueryObserverOptions<
options: InfiniteQueryObserverOptions<
TQueryFnData,
TError,
TData,
Expand Down
2 changes: 1 addition & 1 deletion packages/query-core/src/mutationObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export class MutationObserver<
| MutationObserverOptions<TData, TError, TVariables, TContext>
| undefined
this.options = this.#client.defaultMutationOptions(options)
if (!shallowEqualObjects(prevOptions, this.options)) {
if (!shallowEqualObjects(this.options, prevOptions)) {
this.#client.getMutationCache().notify({
type: 'observerOptionsUpdated',
mutation: this.#currentMutation,
Expand Down
7 changes: 5 additions & 2 deletions packages/query-core/src/queryCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,13 @@ export class QueryCache extends Subscribable<QueryCacheListener> {

build<TQueryFnData, TError, TData, TQueryKey extends QueryKey>(
client: QueryClient,
options: QueryOptions<TQueryFnData, TError, TData, TQueryKey>,
options: WithRequired<
QueryOptions<TQueryFnData, TError, TData, TQueryKey>,
'queryKey'
>,
state?: QueryState<TData, TError>,
): Query<TQueryFnData, TError, TData, TQueryKey> {
const queryKey = options.queryKey!
const queryKey = options.queryKey
const queryHash =
options.queryHash ?? hashQueryKeyByOptions(queryKey, options)
let query = this.get<TQueryFnData, TError, TData, TQueryKey>(queryHash)
Expand Down
15 changes: 9 additions & 6 deletions packages/query-core/src/queryClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import type { MutationFilters, QueryFilters, Updater } from './utils'

interface QueryDefaults {
queryKey: QueryKey
defaultOptions: QueryOptions<any, any, any>
defaultOptions: Omit<QueryOptions<any, any, any>, 'queryKey'>
}

interface MutationDefaults {
Expand Down Expand Up @@ -409,10 +409,13 @@ export class QueryClient {

getQueryDefaults(
queryKey: QueryKey,
): QueryObserverOptions<any, any, any, any, any> {
): Omit<QueryObserverOptions<any, any, any, any, any>, 'queryKey'> {
const defaults = [...this.#queryDefaults.values()]

let result: QueryObserverOptions<any, any, any, any, any> = {}
let result: Omit<
QueryObserverOptions<any, any, any, any, any>,
'queryKey'
> = {}

defaults.forEach((queryDefault) => {
if (partialMatchKey(queryKey, queryDefault.queryKey)) {
Expand Down Expand Up @@ -456,7 +459,7 @@ export class QueryClient {
TQueryKey extends QueryKey = QueryKey,
TPageParam = never,
>(
options?:
options:
| QueryObserverOptions<
TQueryFnData,
TError,
Expand All @@ -479,7 +482,7 @@ export class QueryClient {
TQueryData,
TQueryKey
> {
if (options?._defaulted) {
if (options._defaulted) {
return options as DefaultedQueryObserverOptions<
TQueryFnData,
TError,
Expand All @@ -491,7 +494,7 @@ export class QueryClient {

const defaultedOptions = {
...this.#defaultOptions.queries,
...(options?.queryKey && this.getQueryDefaults(options.queryKey)),
...this.getQueryDefaults(options.queryKey),
...options,
_defaulted: true,
}
Expand Down
9 changes: 2 additions & 7 deletions packages/query-core/src/queryObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export class QueryObserver<
}

setOptions(
options?: QueryObserverOptions<
options: QueryObserverOptions<
TQueryFnData,
TError,
TData,
Expand All @@ -146,7 +146,7 @@ export class QueryObserver<

this.options = this.#client.defaultQueryOptions(options)

if (!shallowEqualObjects(prevOptions, this.options)) {
if (!shallowEqualObjects(this.options, prevOptions)) {
this.#client.getQueryCache().notify({
type: 'observerOptionsUpdated',
query: this.#currentQuery,
Expand All @@ -161,11 +161,6 @@ export class QueryObserver<
throw new Error('Expected enabled to be a boolean')
}

// Keep previous query key if the user does not supply one
if (!this.options.queryKey) {
this.options.queryKey = prevOptions.queryKey
}

this.#updateQuery()

const mounted = this.hasListeners()
Expand Down
25 changes: 0 additions & 25 deletions packages/query-core/src/tests/queriesObserver.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,31 +34,6 @@ describe('queriesObserver', () => {
expect(observerResult).toMatchObject([{ data: 1 }, { data: 2 }])
})

test('should still return value for undefined query key', async () => {
const consoleMock = vi.spyOn(console, 'error')
consoleMock.mockImplementation(() => undefined)
const key1 = queryKey()
const queryFn1 = vi.fn().mockReturnValue(1)
const queryFn2 = vi.fn().mockReturnValue(2)
const observer = new QueriesObserver(queryClient, [
{ queryKey: key1, queryFn: queryFn1 },
{ queryKey: undefined, queryFn: queryFn2 },
])
let observerResult
const unsubscribe = observer.subscribe((result) => {
observerResult = result
})
await sleep(1)
unsubscribe()
expect(observerResult).toMatchObject([{ data: 1 }, { data: 2 }])

expect(consoleMock).toHaveBeenCalledTimes(1)
expect(consoleMock).toHaveBeenCalledWith(
"As of v4, queryKey needs to be an Array. If you are using a string like 'repoData', please change it to an Array, e.g. ['repoData']",
)
consoleMock.mockRestore()
})

test('should update when a query updates', async () => {
const key1 = queryKey()
const key2 = queryKey()
Expand Down
3 changes: 2 additions & 1 deletion packages/query-core/src/tests/queryCache.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,11 @@ describe('queryCache', () => {
const unsubScribeObserver = observer.subscribe(vi.fn())

await waitFor(() => {
expect(events.length).toBe(8)
expect(events.length).toBe(9)
})

expect(events).toEqual([
'observerOptionsUpdated',
'added', // 1. Query added -> loading
'observerResultsUpdated', // 2. Observer result updated -> loading
'observerAdded', // 3. Observer added
Expand Down
6 changes: 3 additions & 3 deletions packages/query-core/src/tests/queryObserver.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ describe('queryObserver', () => {
const unsubscribe = observer.subscribe((x) => {
results.push(x)
})
observer.setOptions({ enabled: false, staleTime: 10 })
observer.setOptions({ queryKey: key, enabled: false, staleTime: 10 })
await queryClient.fetchQuery({ queryKey: key, queryFn })
await sleep(100)
unsubscribe()
Expand Down Expand Up @@ -579,7 +579,7 @@ describe('queryObserver', () => {

const firstData = observer.getCurrentResult().data

observer.setOptions({ placeholderData: {} })
observer.setOptions({ queryKey: key, placeholderData: {} })

const secondData = observer.getCurrentResult().data

Expand Down Expand Up @@ -885,7 +885,7 @@ describe('queryObserver', () => {

const spy = vi.fn()
const unsubscribe = queryClient.getQueryCache().subscribe(spy)
observer.setOptions({ enabled: false })
observer.setOptions({ queryKey: key, enabled: false })

expect(spy).toHaveBeenCalledTimes(1)
expect(spy).toHaveBeenCalledWith(
Expand Down
2 changes: 2 additions & 0 deletions packages/query-core/src/tests/queryObserver.types.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ describe('placeholderData', () => {
}

new QueryObserver<boolean, CustomError>(createQueryClient(), {
queryKey: ['key'],
placeholderData: (_, previousQuery) => {
const error = previousQuery?.state.error

Expand All @@ -49,6 +50,7 @@ describe('placeholderData', () => {
type QueryData = typeof queryData

new QueryObserver(createQueryClient(), {
queryKey: ['key'],
queryFn: () => queryData,
select: (data) => data.foo,
placeholderData: (previousData) => {
Expand Down
18 changes: 18 additions & 0 deletions packages/query-core/src/tests/utils.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,29 @@ import {
matchMutation,
partialMatchKey,
replaceEqualDeep,
shallowEqualObjects,
} from '../utils'
import { Mutation } from '../mutation'
import { createQueryClient } from './utils'

describe('core/utils', () => {
describe('shallowEqualObjects', () => {
it('should return `true` for shallow equal objects', () => {
expect(shallowEqualObjects({ a: 1 }, { a: 1 })).toEqual(true)
})

it('should return `false` for non shallow equal objects', () => {
expect(shallowEqualObjects({ a: 1 }, { a: 2 })).toEqual(false)
})

it('should return `false` if lengths are not equal', () => {
expect(shallowEqualObjects({ a: 1 }, { a: 1, b: 2 })).toEqual(false)
})

it('should return false if b is undefined', () => {
expect(shallowEqualObjects({ a: 1 }, undefined)).toEqual(false)
})
})
describe('isPlainObject', () => {
it('should return `true` for a plain object', () => {
expect(isPlainObject({})).toEqual(true)
Expand Down
16 changes: 9 additions & 7 deletions packages/query-core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,12 +218,9 @@ export interface QueryObserverOptions<
TQueryData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
TPageParam = never,
> extends QueryOptions<
TQueryFnData,
TError,
TQueryData,
TQueryKey,
TPageParam
> extends WithRequired<
QueryOptions<TQueryFnData, TError, TQueryData, TQueryKey, TPageParam>,
'queryKey'
> {
/**
* Set this to `false` to disable automatic refetching when the query mounts or changes query keys.
Expand Down Expand Up @@ -340,6 +337,11 @@ export interface QueryObserverOptions<
export type WithRequired<TTarget, TKey extends keyof TTarget> = TTarget & {
[_ in TKey]: {}
}
export type Optional<TTarget, TKey extends keyof TTarget> = Pick<
Partial<TTarget>,
TKey
> &
Omit<TTarget, TKey>

export type DefaultedQueryObserverOptions<
TQueryFnData = unknown,
Expand Down Expand Up @@ -871,7 +873,7 @@ export interface QueryClientConfig {
}

export interface DefaultOptions<TError = DefaultError> {
queries?: Omit<QueryObserverOptions<unknown, TError>, 'suspense'>
queries?: Omit<QueryObserverOptions<unknown, TError>, 'suspense' | 'queryKey'>
mutations?: MutationObserverOptions<unknown, TError, unknown, unknown>
}

Expand Down
11 changes: 7 additions & 4 deletions packages/query-core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export function matchMutation(

export function hashQueryKeyByOptions<TQueryKey extends QueryKey = QueryKey>(
queryKey: TQueryKey,
options?: QueryOptions<any, any, any, TQueryKey>,
options?: Pick<QueryOptions<any, any, any, any>, 'queryKeyHashFn'>,
): string {
const hashFn = options?.queryKeyHashFn || hashKey
return hashFn(queryKey)
Expand Down Expand Up @@ -257,10 +257,13 @@ export function replaceEqualDeep(a: any, b: any): any {
}

/**
* Shallow compare objects. Only works with objects that always have the same properties.
* Shallow compare objects.
*/
export function shallowEqualObjects<T>(a: T, b: T): boolean {
if ((a && !b) || (b && !a)) {
export function shallowEqualObjects<T extends Record<string, any>>(
a: T,
b: T | undefined,
): boolean {
if (!b || Object.keys(a).length !== Object.keys(b).length) {
return false
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ describe('persistQueryClientSave', () => {
const queryFn = vi.fn().mockReturnValue(1)
const observer = new QueriesObserver(queryClient, [{ queryKey, queryFn }])
const unsubscribeObserver = observer.subscribe(vi.fn())
observer.getObservers()[0]?.setOptions({ refetchOnWindowFocus: false })
observer
.getObservers()[0]
?.setOptions({ queryKey, refetchOnWindowFocus: false })
unsubscribeObserver()

queryClient.setQueryData(queryKey, 2)
Expand Down
Loading

0 comments on commit 61f4d74

Please sign in to comment.