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

fix(query-core): Show correct placeholderData when request in cache #6357

Merged
merged 3 commits into from
Nov 11, 2023
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
7 changes: 4 additions & 3 deletions packages/query-core/src/queryObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -592,14 +592,15 @@ export class QueryObserver<
this.#currentResultState = this.#currentQuery.state
this.#currentResultOptions = this.options

if (this.#currentResultState.data !== undefined) {
this.#lastQueryWithDefinedData = this.#currentQuery
}

// Only notify and update result if something has changed
if (shallowEqualObjects(nextResult, prevResult)) {
return
}

if (this.#currentResultState.data !== undefined) {
this.#lastQueryWithDefinedData = this.#currentQuery
}
this.#currentResult = nextResult

// Determine which callbacks to trigger
Expand Down
95 changes: 95 additions & 0 deletions packages/react-query/src/__tests__/useQuery.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6226,6 +6226,101 @@ describe('useQuery', () => {
await waitFor(() => rendered.getByText('Works'))
})

it('should keep the previous data when placeholderData is set and cache is used', async () => {
const key = queryKey()
const states: Array<UseQueryResult<number | undefined>> = []
const steps = [0, 1, 0, 2]

function Page() {
const [count, setCount] = React.useState(0)

const state = useQuery({
staleTime: Infinity,
queryKey: [key, steps[count]],
queryFn: async () => {
await sleep(10)
return steps[count]
},
placeholderData: keepPreviousData,
})

states.push(state)

return (
<div>
<div>data: {state.data}</div>
<button onClick={() => setCount((c) => c + 1)}>setCount</button>
</div>
)
}

const rendered = renderWithClient(queryClient, <Page />)

await waitFor(() => rendered.getByText('data: 0'))

fireEvent.click(rendered.getByRole('button', { name: 'setCount' }))

await waitFor(() => rendered.getByText('data: 1'))

fireEvent.click(rendered.getByRole('button', { name: 'setCount' }))

await waitFor(() => rendered.getByText('data: 0'))

fireEvent.click(rendered.getByRole('button', { name: 'setCount' }))

await waitFor(() => rendered.getByText('data: 2'))

// Initial
expect(states[0]).toMatchObject({
data: undefined,
isFetching: true,
isSuccess: false,
isPlaceholderData: false,
})
// Fetched
expect(states[1]).toMatchObject({
data: 0,
isFetching: false,
isSuccess: true,
isPlaceholderData: false,
})
// Set state
expect(states[2]).toMatchObject({
data: 0,
isFetching: true,
isSuccess: true,
isPlaceholderData: true,
})
// New data
expect(states[3]).toMatchObject({
data: 1,
isFetching: false,
isSuccess: true,
isPlaceholderData: false,
})
// Set state with existing data
expect(states[4]).toMatchObject({
data: 0,
isFetching: false,
isSuccess: true,
isPlaceholderData: false,
})
// Set state where the placeholder value should come from cache request
expect(states[5]).toMatchObject({
data: 0,
isFetching: true,
isSuccess: true,
isPlaceholderData: true,
})
// New data
expect(states[6]).toMatchObject({
data: 2,
isFetching: false,
isSuccess: true,
isPlaceholderData: false,
})
})

// For Project without TS, when migrating from v4 to v5, make sure invalid calls due to bad parameters are tracked.
it('should throw in case of bad arguments to enhance DevX', async () => {
// Mock console error to avoid noise when test is run
Expand Down