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

Update method query observer base result #3636

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/src/pages/reference/useQuery.md
Expand Up @@ -25,6 +25,7 @@ const {
isSuccess,
refetch,
remove,
update,
status,
} = useQuery(queryKey, queryFn?, {
cacheTime,
Expand Down Expand Up @@ -246,3 +247,7 @@ const result = useQuery({
- If `cancelRefetch` is `true`, then the current request will be cancelled before a new request is made
- `remove: () => void`
- A function to remove the query from the cache.
- `update: (updater: TData | (oldData: TData | undefined) => TData) => void`
- A convenience function to optimistically update the query.
- It calls [`queryClient.setQueryData`](../reference/QueryClient#queryclientsetquerydata) internally with the key that was provided to the `useQuery` hook
- If its called without providing a query key to the `useQuery` hook, it will throw a warning in the console
16 changes: 15 additions & 1 deletion src/core/queryObserver.ts
@@ -1,4 +1,4 @@
import { RefetchQueryFilters } from './types'
import { RefetchQueryFilters, SetDataOptions } from './types'
import {
isServer,
isValidTimeout,
Expand Down Expand Up @@ -99,6 +99,7 @@ export class QueryObserver<
protected bindMethods(): void {
this.remove = this.remove.bind(this)
this.refetch = this.refetch.bind(this)
this.update = this.update.bind(this)
}

protected onSubscribe(): void {
Expand Down Expand Up @@ -311,6 +312,18 @@ export class QueryObserver<
})
}

update(updater: any, setDataOptions?: SetDataOptions | undefined) {
if (this?.options?.queryKey) {
return this.client.setQueryData(
this.options.queryKey,
updater,
setDataOptions
)
}

getLogger().warn('Called update on a query without queryKey being set')
}

fetchOptimistic(
options: QueryObserverOptions<
TQueryFnData,
Expand Down Expand Up @@ -604,6 +617,7 @@ export class QueryObserver<
isStale: isStale(query, options),
refetch: this.refetch,
remove: this.remove,
update: this.update,
}

return result as QueryObserverResult<TData, TError>
Expand Down
14 changes: 14 additions & 0 deletions src/core/tests/queryObserver.test.tsx
Expand Up @@ -814,4 +814,18 @@ describe('queryObserver', () => {

unsubscribe()
})

test('optimistically update result after calling the update method', async () => {
const key = queryKey()
const observer = new QueryObserver(queryClient, {
queryKey: key,
queryFn: () => Promise.reject({ count: 1 }),
})

observer.update({ count: 2 })

expect(observer.getOptimisticResult(observer.options)).toMatchObject({
data: { count: 2 },
})
})
})
1 change: 1 addition & 0 deletions src/core/types.ts
Expand Up @@ -346,6 +346,7 @@ export interface QueryObserverBaseResult<TData = unknown, TError = unknown> {
options?: RefetchOptions & RefetchQueryFilters<TPageData>
) => Promise<QueryObserverResult<TData, TError>>
remove: () => void
update: (updater: any, setDataOptions?: SetDataOptions | undefined) => void
status: QueryStatus
}

Expand Down
2 changes: 2 additions & 0 deletions src/react/tests/useInfiniteQuery.test.tsx
Expand Up @@ -95,6 +95,7 @@ describe('useInfiniteQuery', () => {
isSuccess: false,
refetch: expect.any(Function),
remove: expect.any(Function),
update: expect.any(Function),
status: 'loading',
})

Expand Down Expand Up @@ -126,6 +127,7 @@ describe('useInfiniteQuery', () => {
isSuccess: true,
refetch: expect.any(Function),
remove: expect.any(Function),
update: expect.any(Function),
status: 'success',
})
})
Expand Down
62 changes: 62 additions & 0 deletions src/react/tests/useQuery.test.tsx
Expand Up @@ -185,6 +185,7 @@ describe('useQuery', () => {
isSuccess: false,
refetch: expect.any(Function),
remove: expect.any(Function),
update: expect.any(Function),
status: 'loading',
})

Expand All @@ -210,6 +211,7 @@ describe('useQuery', () => {
isSuccess: true,
refetch: expect.any(Function),
remove: expect.any(Function),
update: expect.any(Function),
status: 'success',
})
})
Expand Down Expand Up @@ -265,6 +267,7 @@ describe('useQuery', () => {
isSuccess: false,
refetch: expect.any(Function),
remove: expect.any(Function),
update: expect.any(Function),
status: 'loading',
})

Expand All @@ -290,6 +293,7 @@ describe('useQuery', () => {
isSuccess: false,
refetch: expect.any(Function),
remove: expect.any(Function),
update: expect.any(Function),
status: 'loading',
})

Expand All @@ -315,6 +319,7 @@ describe('useQuery', () => {
isSuccess: false,
refetch: expect.any(Function),
remove: expect.any(Function),
update: expect.any(Function),
status: 'error',
})

Expand Down Expand Up @@ -4871,4 +4876,61 @@ describe('useQuery', () => {

consoleMock.mockRestore()
})
it('should optimistically update query after calling update', async () => {
const key = queryKey()
const states: UseQueryResult<string>[] = []

function Page() {
const state = useQuery<string>(key, () => Promise.resolve('data'))
states.push(state)
return (
<>
<button onClick={() => state.update('updated')}>update</button>
<div>{state.data}</div>
</>
)
}

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

const fetchBtn = rendered.getByRole('button', { name: 'update' })
await waitFor(() => rendered.getByText('data'))
fireEvent.click(fetchBtn)
await waitFor(() => rendered.getByText('updated'))

expect(states[0]).toMatchObject({ data: undefined })
expect(states[1]).toMatchObject({ data: 'data' })
expect(states[2]).toMatchObject({ data: 'updated' })
expect(queryClient.getQueryData(key)).toBe('updated')
expect(queryCache.find(key)?.state.data).toBe('updated')
})

it('should optimistically update query after calling update with an updater function', async () => {
const key = queryKey()
const states: UseQueryResult<string>[] = []

function Page() {
const state = useQuery<string>(key, () => Promise.resolve('data'))
states.push(state)
return (
<>
<button onClick={() => state.update((old: string) => 'new ' + old)}>update</button>
<div>{state.data}</div>
</>
)
}

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

const fetchBtn = rendered.getByRole('button', { name: 'update' })
await waitFor(() => rendered.getByText('data'))
fireEvent.click(fetchBtn)
await waitFor(() => rendered.getByText('new data'))

expect(states[0]).toMatchObject({ data: undefined })
expect(states[1]).toMatchObject({ data: 'data' })
expect(states[2]).toMatchObject({ data: 'new data' })
expect(queryClient.getQueryData(key)).toBe('new data')
expect(queryCache.find(key)?.state.data).toBe('new data')
})
})