Skip to content
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

Feature/stale time on query #8313

Closed
wants to merge 29 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
bd7bf49
fix: move staleTime from queryObserver to query
TkDodo Nov 18, 2024
22f68d2
fix: do not fetch disabled observers on mount
TkDodo Nov 18, 2024
36f53e7
refactor: always resolve staleTime to a number
TkDodo Nov 18, 2024
d6ff692
refactor: move resolveStaleTime into the query itself
TkDodo Nov 18, 2024
6133fb6
refactor: make 0 per default an invalid timeout
TkDodo Nov 18, 2024
8e31ddd
fix: adjust test
TkDodo Nov 18, 2024
54198b2
chore: leftover
TkDodo Nov 18, 2024
e773cd9
fix: un-set invalidated state if we get a new staleTime
TkDodo Nov 18, 2024
27cf824
fix: also handle the case where staleTime goes from a high number to …
TkDodo Nov 18, 2024
39b81d4
fix: staleTime:Infinity should also un-set `isInvalidated`
TkDodo Nov 18, 2024
6c629ce
chore: remove logs
TkDodo Nov 18, 2024
a57bc55
fix: "silence" update events that happen before queries were added to…
TkDodo Nov 18, 2024
30735d9
fix: there's now an extra update event on the cache (expected)
TkDodo Nov 18, 2024
2747f7e
test: queries are now invalidated with staleTime 0 (expected)
TkDodo Nov 18, 2024
08920a9
test: queries without staleTime are now instantly invalidated (expected)
TkDodo Nov 18, 2024
92a109d
fix: do not call setOptions when getting from `build`
TkDodo Nov 18, 2024
f91fb77
fix: queries without data are always stale
TkDodo Nov 20, 2024
c323017
test: queries without data are always stale, even if the observer is …
TkDodo Nov 20, 2024
4cc1d9b
refactor: re-use variable
TkDodo Nov 20, 2024
b07b3fa
chore: remove a test that makes no sense anymore
TkDodo Nov 20, 2024
d18318f
Merge branch 'main' into feature/staleTime-on-query
TkDodo Nov 20, 2024
057b59e
refactor(query): make sure to always update staleTimeout in the const…
TkDodo Nov 20, 2024
8c1c028
chore: inline leftover
TkDodo Nov 20, 2024
0c4040e
Merge branch 'main' into feature/staleTime-on-query
TkDodo Nov 20, 2024
3ae6bff
feat: warn when there are multiple observers mounted at the same time…
TkDodo Nov 20, 2024
fcea9bf
Merge branch 'main' into feature/staleTime-on-query
TkDodo Nov 20, 2024
33058d1
fix(types): There's no need to define staleTime additionally on Fetch…
TkDodo Nov 22, 2024
6529df2
test: add another case
TkDodo Nov 22, 2024
ce2847c
Merge branch 'main' into feature/staleTime-on-query
TkDodo Nov 23, 2024
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
4 changes: 2 additions & 2 deletions packages/query-core/src/__tests__/hydration.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -891,7 +891,7 @@ describe('dehydration and rehydration', () => {
fetchFailureReason: null,
fetchMeta: null,
fetchStatus: 'fetching',
isInvalidated: false,
isInvalidated: true,
status: 'pending',
},
)
Expand All @@ -910,7 +910,7 @@ describe('dehydration and rehydration', () => {
fetchFailureReason: null,
fetchMeta: null,
fetchStatus: 'idle',
isInvalidated: false,
isInvalidated: true,
status: 'success',
}),
)
Expand Down
57 changes: 56 additions & 1 deletion packages/query-core/src/__tests__/query.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,6 @@ describe('query', () => {
// Promise should eventually be resolved
await promise

console.log('has finished')
expect(result).toBe('data3')
onlineMock.mockRestore()
})
Expand Down Expand Up @@ -1046,4 +1045,60 @@ describe('query', () => {

expect(query.state.status).toBe('error')
})

it('should warn when multiple observers have different staleTimes when mounted at the same time', async () => {
const consoleMock = vi.spyOn(console, 'error')
consoleMock.mockImplementation(() => undefined)
const key = queryKey()
const observer1 = new QueryObserver(queryClient, {
queryKey: key,
initialData: 5,
staleTime: 1000,
})

const unsubscribe1 = observer1.subscribe(() => undefined)

const observer2 = new QueryObserver(queryClient, {
queryKey: key,
initialData: 5,
staleTime: 2000,
})

const unsubscribe2 = observer2.subscribe(() => undefined)

expect(consoleMock).toHaveBeenCalledTimes(1)
expect(consoleMock).toHaveBeenCalledWith(
`Multiple observers with different staleTimes are attached to the this query: ["${key}"]. The staleTime of the query will be determined by the last observer.`,
)
unsubscribe1()
unsubscribe2()
consoleMock.mockRestore()
})

it('should not warn when multiple observers have different staleTimes but not mounted at the same time', async () => {
const consoleMock = vi.spyOn(console, 'error')
consoleMock.mockImplementation(() => undefined)
const key = queryKey()
const observer1 = new QueryObserver(queryClient, {
queryKey: key,
initialData: 5,
staleTime: 1000,
})

const unsubscribe1 = observer1.subscribe(() => undefined)

unsubscribe1()

const observer2 = new QueryObserver(queryClient, {
queryKey: key,
initialData: 5,
staleTime: 2000,
})

const unsubscribe2 = observer2.subscribe(() => undefined)

expect(consoleMock).toHaveBeenCalledTimes(0)
unsubscribe2()
consoleMock.mockRestore()
})
})
5 changes: 4 additions & 1 deletion packages/query-core/src/__tests__/queryCache.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ describe('queryCache', () => {
const unsubScribeObserver = observer.subscribe(vi.fn())

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

expect(events).toEqual([
Expand All @@ -67,6 +67,7 @@ describe('queryCache', () => {
'observerResultsUpdated', // 6. Observer result updated -> success
'updated', // 7. Query updated -> success
'observerResultsUpdated', // 8. Observer result updated -> stale
'updated', // 9. Query updated -> stale
])

queries.forEach((query) => {
Expand Down Expand Up @@ -165,6 +166,7 @@ describe('queryCache', () => {
await queryClient.prefetchQuery({
queryKey: key1,
queryFn: () => 'data1',
staleTime: 100,
})
await queryClient.prefetchQuery({
queryKey: key2,
Expand All @@ -173,6 +175,7 @@ describe('queryCache', () => {
await queryClient.prefetchQuery({
queryKey: [{ a: 'a', b: 'b' }],
queryFn: () => 'data3',
staleTime: 100,
})
await queryClient.prefetchQuery({
queryKey: ['posts', 1],
Expand Down
74 changes: 61 additions & 13 deletions packages/query-core/src/__tests__/queryClient.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -655,49 +655,85 @@ describe('queryClient', () => {
const key = queryKey()

queryClient.setQueryData(key, 'og')
const fetchFn = () => Promise.resolve('new')
const fetchFn = vi.fn().mockImplementation(() => Promise.resolve('new'))
const first = await queryClient.fetchQuery({
queryKey: key,
queryFn: fetchFn,
initialData: 'initial',
staleTime: 100,
})
expect(first).toBe('og')
expect(fetchFn).toHaveBeenCalledTimes(0)
})

test('should only fetch if the data is older then the given stale time', async () => {
const key = queryKey()

let count = 0
const fetchFn = () => ++count
const fetchFn = vi.fn().mockImplementation(() => null)

queryClient.setQueryData(key, count)
const first = await queryClient.fetchQuery({
queryClient.setQueryData(key, 'data')
await queryClient.fetchQuery({
queryKey: key,
queryFn: fetchFn,
staleTime: 100,
})
// no fetch because we have fresh data (not older than 100ms)
expect(fetchFn).toHaveBeenCalledTimes(0)
await sleep(11)
const second = await queryClient.fetchQuery({
await queryClient.fetchQuery({
queryKey: key,
queryFn: fetchFn,
staleTime: 10,
})
const third = await queryClient.fetchQuery({
// fetch because staleTime is now 10ms and we have waited 11ms already
expect(fetchFn).toHaveBeenCalledTimes(1)
await queryClient.fetchQuery({
queryKey: key,
queryFn: fetchFn,
staleTime: 10,
})
// no additional fetch because the data is still fresh
expect(fetchFn).toHaveBeenCalledTimes(1)
await sleep(11)
const fourth = await queryClient.fetchQuery({
await queryClient.fetchQuery({
queryKey: key,
queryFn: fetchFn,
staleTime: 10,
})
expect(first).toBe(0)
expect(second).toBe(1)
expect(third).toBe(1)
expect(fourth).toBe(2)
// another fetch after another sleep because the data is now stale
expect(fetchFn).toHaveBeenCalledTimes(2)
})

test('should fetch if data has been manually invalidated', async () => {
const key = queryKey()

const fetchFn = vi.fn().mockImplementation(() => null)

queryClient.setQueryDefaults(key, { staleTime: Infinity })
queryClient.setQueryData(key, 'data')

await queryClient.fetchQuery({
queryKey: key,
queryFn: fetchFn,
staleTime: 100,
})
// no fetch because we have fresh data (not older than 100ms)
expect(fetchFn).toHaveBeenCalledTimes(0)

// mark query as invalidated
await queryClient.invalidateQueries({
queryKey: key,
refetchType: 'none',
})

await queryClient.fetchQuery({
queryKey: key,
queryFn: fetchFn,
staleTime: 100,
})

// fetch because even if not stale by time, it's manually invalidated
expect(fetchFn).toHaveBeenCalledTimes(1)
})
})

Expand Down Expand Up @@ -1090,14 +1126,23 @@ describe('queryClient', () => {
test('should be able to refetch all stale queries', async () => {
const key1 = queryKey()
const key2 = queryKey()
const key3 = queryKey()
const queryFn1 = vi
.fn<(...args: Array<unknown>) => string>()
.mockReturnValue('data1')
const queryFn2 = vi
.fn<(...args: Array<unknown>) => string>()
.mockReturnValue('data2')
const queryFn3 = vi
.fn<(...args: Array<unknown>) => string>()
.mockReturnValue('data3')
await queryClient.fetchQuery({ queryKey: key1, queryFn: queryFn1 })
await queryClient.fetchQuery({ queryKey: key2, queryFn: queryFn2 })
await queryClient.fetchQuery({
queryKey: key3,
queryFn: queryFn3,
staleTime: 1,
})
const observer = new QueryObserver(queryClient, {
queryKey: key1,
queryFn: queryFn1,
Expand All @@ -1108,7 +1153,10 @@ describe('queryClient', () => {
unsubscribe()
// fetchQuery, observer mount, invalidation (cancels observer mount) and refetch
expect(queryFn1).toHaveBeenCalledTimes(4)
expect(queryFn2).toHaveBeenCalledTimes(1)
// fetchQuery, refetch
expect(queryFn2).toHaveBeenCalledTimes(2)
// fetchQuery
expect(queryFn3).toHaveBeenCalledTimes(1)
})

test('should be able to refetch all stale and active queries', async () => {
Expand Down
8 changes: 4 additions & 4 deletions packages/query-core/src/__tests__/queryObserver.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -663,12 +663,12 @@ describe('queryObserver', () => {
results.push(x)
})
observer.setOptions({ queryKey: key, enabled: false, staleTime: 10 })
await queryClient.fetchQuery({ queryKey: key, queryFn })
await queryClient.fetchQuery({ queryKey: key, queryFn, staleTime: 1000 })
await sleep(20)
unsubscribe()
expect(queryFn).toHaveBeenCalledTimes(1)
expect(results.length).toBe(2)
expect(results[0]).toMatchObject({ isStale: false, data: undefined })
expect(results[0]).toMatchObject({ isStale: true, data: undefined })
expect(results[1]).toMatchObject({ isStale: false, data: 'data' })
})

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

test('disabled observers should not be stale', async () => {
test('disabled observers should also be stale', async () => {
const key = queryKey()

const observer = new QueryObserver(queryClient, {
Expand All @@ -1111,7 +1111,7 @@ describe('queryObserver', () => {
})

const result = observer.getCurrentResult()
expect(result.isStale).toBe(false)
expect(result.isStale).toBe(true)
})

test('should allow staleTime as a function', async () => {
Expand Down
10 changes: 10 additions & 0 deletions packages/query-core/src/__tests__/utils.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
partialMatchKey,
replaceEqualDeep,
shallowEqualObjects,
timeUntilStale,
} from '../utils'
import { Mutation } from '../mutation'
import { createQueryClient } from './utils'
Expand All @@ -30,6 +31,15 @@ describe('core/utils', () => {
expect(shallowEqualObjects({ a: 1 }, undefined)).toEqual(false)
})
})
describe('timeUntilStale', () => {
it('should work with Infinity', () => {
expect(timeUntilStale(25, Infinity)).toBe(Infinity)
})
it('should never go below zero', () => {
expect(timeUntilStale(25, -1)).toBe(0)
expect(timeUntilStale(25, -Infinity)).toBe(0)
})
})
describe('isPlainObject', () => {
it('should return `true` for a plain object', () => {
expect(isPlainObject({})).toEqual(true)
Expand Down
Loading
Loading