Skip to content

Commit 1614c31

Browse files
authored
fix(core): initialData should take precedence over keepPreviousData (#4422)
previousData is data that we want to show from a previous observer if we don't have any good data in the cache already. InitialData is always considered good data, just as if it were data that has been fetched. It might be potentially stale, but that's something that can be controlled via staleTime and initialDataUpdatedAt. So that means we shouldn't show previousData if we also have initialData
1 parent c376158 commit 1614c31

File tree

4 files changed

+104
-27
lines changed

4 files changed

+104
-27
lines changed

packages/query-core/src/query.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -600,16 +600,14 @@ function getDefaultState<
600600
? (options.initialData as InitialDataFunction<TData>)()
601601
: options.initialData
602602

603-
const hasInitialData = typeof options.initialData !== 'undefined'
603+
const hasData = typeof data !== 'undefined'
604604

605-
const initialDataUpdatedAt = hasInitialData
605+
const initialDataUpdatedAt = hasData
606606
? typeof options.initialDataUpdatedAt === 'function'
607607
? (options.initialDataUpdatedAt as () => number | undefined)()
608608
: options.initialDataUpdatedAt
609609
: 0
610610

611-
const hasData = typeof data !== 'undefined'
612-
613611
return {
614612
data,
615613
dataUpdateCount: 0,

packages/query-core/src/queryObserver.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,7 @@ export class QueryObserver<
468468
// Keep previous data if needed
469469
if (
470470
options.keepPreviousData &&
471-
!state.dataUpdateCount &&
471+
!state.dataUpdatedAt &&
472472
prevQueryResult?.isSuccess &&
473473
status !== 'error'
474474
) {

packages/react-query/src/__tests__/useQuery.test.tsx

Lines changed: 80 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1769,18 +1769,30 @@ describe('useQuery', () => {
17691769

17701770
states.push(state)
17711771

1772-
React.useEffect(() => {
1773-
setActTimeout(() => {
1774-
setCount(1)
1775-
}, 20)
1776-
}, [])
1777-
1778-
return null
1772+
return (
1773+
<div>
1774+
<h1>
1775+
data: {state.data}, count: {count}, isFetching:{' '}
1776+
{String(state.isFetching)}
1777+
</h1>
1778+
<button onClick={() => setCount(1)}>inc</button>
1779+
</div>
1780+
)
17791781
}
17801782

1781-
renderWithClient(queryClient, <Page />)
1783+
const rendered = renderWithClient(queryClient, <Page />)
17821784

1783-
await waitFor(() => expect(states.length).toBe(5))
1785+
await waitFor(() =>
1786+
rendered.getByText('data: 0, count: 0, isFetching: false'),
1787+
)
1788+
1789+
fireEvent.click(rendered.getByRole('button', { name: 'inc' }))
1790+
1791+
await waitFor(() =>
1792+
rendered.getByText('data: 1, count: 1, isFetching: false'),
1793+
)
1794+
1795+
expect(states.length).toBe(5)
17841796

17851797
// Initial
17861798
expect(states[0]).toMatchObject({
@@ -1798,17 +1810,17 @@ describe('useQuery', () => {
17981810
})
17991811
// Set state
18001812
expect(states[2]).toMatchObject({
1801-
data: 0,
1813+
data: 99,
18021814
isFetching: true,
18031815
isSuccess: true,
1804-
isPreviousData: true,
1816+
isPreviousData: false,
18051817
})
18061818
// Hook state update
18071819
expect(states[3]).toMatchObject({
1808-
data: 0,
1820+
data: 99,
18091821
isFetching: true,
18101822
isSuccess: true,
1811-
isPreviousData: true,
1823+
isPreviousData: false,
18121824
})
18131825
// New data
18141826
expect(states[4]).toMatchObject({
@@ -3733,6 +3745,61 @@ describe('useQuery', () => {
37333745
expect(results[1]).toMatchObject({ data: 1, isFetching: false })
37343746
})
37353747

3748+
it('should show the correct data when switching keys with initialData, keepPreviousData & staleTime', async () => {
3749+
const key = queryKey()
3750+
3751+
const ALL_TODOS = [
3752+
{ name: 'todo A', priority: 'high' },
3753+
{ name: 'todo B', priority: 'medium' },
3754+
]
3755+
3756+
const initialTodos = ALL_TODOS
3757+
3758+
function Page() {
3759+
const [filter, setFilter] = React.useState('')
3760+
const { data: todos } = useQuery(
3761+
[...key, filter],
3762+
async () => {
3763+
return ALL_TODOS.filter((todo) =>
3764+
filter ? todo.priority === filter : true,
3765+
)
3766+
},
3767+
{
3768+
initialData() {
3769+
return filter === '' ? initialTodos : undefined
3770+
},
3771+
keepPreviousData: true,
3772+
staleTime: 5000,
3773+
},
3774+
)
3775+
3776+
return (
3777+
<div>
3778+
Current Todos, filter: {filter || 'all'}
3779+
<hr />
3780+
<button onClick={() => setFilter('')}>All</button>
3781+
<button onClick={() => setFilter('high')}>High</button>
3782+
<ul>
3783+
{(todos ?? []).map((todo) => (
3784+
<li key={todo.name}>
3785+
{todo.name} - {todo.priority}
3786+
</li>
3787+
))}
3788+
</ul>
3789+
</div>
3790+
)
3791+
}
3792+
3793+
const rendered = renderWithClient(queryClient, <Page />)
3794+
3795+
await waitFor(() => rendered.getByText('Current Todos, filter: all'))
3796+
3797+
fireEvent.click(rendered.getByRole('button', { name: /high/i }))
3798+
await waitFor(() => rendered.getByText('Current Todos, filter: high'))
3799+
fireEvent.click(rendered.getByRole('button', { name: /all/i }))
3800+
await waitFor(() => rendered.getByText('todo B - medium'))
3801+
})
3802+
37363803
// // See https://github.com/tannerlinsley/react-query/issues/214
37373804
it('data should persist when enabled is changed to false', async () => {
37383805
const key = queryKey()

packages/solid-query/src/__tests__/createQuery.test.tsx

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1993,13 +1993,15 @@ describe('createQuery', () => {
19931993
states.push({ ...state })
19941994
})
19951995

1996-
createEffect(() => {
1997-
setActTimeout(() => {
1998-
setCount(1)
1999-
}, 20)
2000-
})
2001-
2002-
return null
1996+
return (
1997+
<div>
1998+
<h1>
1999+
data: {state.data}, count: {count}, isFetching:{' '}
2000+
{String(state.isFetching)}
2001+
</h1>
2002+
<button onClick={() => setCount(1)}>inc</button>
2003+
</div>
2004+
)
20032005
}
20042006

20052007
render(() => (
@@ -2008,6 +2010,16 @@ describe('createQuery', () => {
20082010
</QueryClientProvider>
20092011
))
20102012

2013+
await waitFor(() =>
2014+
screen.getByText('data: 0, count: 0, isFetching: false'),
2015+
)
2016+
2017+
fireEvent.click(screen.getByRole('button', { name: 'inc' }))
2018+
2019+
await waitFor(() =>
2020+
screen.getByText('data: 1, count: 1, isFetching: false'),
2021+
)
2022+
20112023
await waitFor(() => expect(states.length).toBe(4))
20122024

20132025
// Initial
@@ -2026,10 +2038,10 @@ describe('createQuery', () => {
20262038
})
20272039
// Set state
20282040
expect(states[2]).toMatchObject({
2029-
data: 0,
2041+
data: 99,
20302042
isFetching: true,
20312043
isSuccess: true,
2032-
isPreviousData: true,
2044+
isPreviousData: false,
20332045
})
20342046
// New data
20352047
expect(states[3]).toMatchObject({

0 commit comments

Comments
 (0)