Skip to content

Commit 250c654

Browse files
committed
Merge remote-tracking branch 'react-query/master' into alpha
# Conflicts: # src/core/query.ts # src/core/queryObserver.ts # src/reactjs/tests/useQuery.test.tsx # src/reactjs/types.ts
2 parents 9a20452 + 93824ec commit 250c654

File tree

6 files changed

+150
-47
lines changed

6 files changed

+150
-47
lines changed

examples/playground/src/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@ function fetchTodoById({ id }) {
397397
new Error(JSON.stringify({ fetchTodoById: { id } }, null, 2))
398398
);
399399
}
400-
resolve(list.find((d) => d.id == id));
400+
resolve(list.find((d) => d.id === id));
401401
}, queryTimeMin + Math.random() * (queryTimeMax - queryTimeMin));
402402
});
403403
}

src/core/query.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,10 @@ export class Query<
572572
fetchStatus: canFetch(this.options.networkMode)
573573
? 'fetching'
574574
: 'paused',
575-
status: !state.dataUpdatedAt ? 'loading' : state.status,
575+
...(!state.dataUpdatedAt && {
576+
error: null,
577+
status: 'loading',
578+
}),
576579
}
577580
case 'success':
578581
return {

src/core/queryObserver.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,10 @@ export class QueryObserver<
6969
>
7070
private previousQueryResult?: QueryObserverResult<TData, TError>
7171
private previousSelectError: TError | null
72-
private previousSelectFn?: (data: TQueryData) => TData
72+
private previousSelect?: {
73+
fn: (data: TQueryData) => TData
74+
result: TData
75+
}
7376
private staleTimeoutId?: ReturnType<typeof setTimeout>
7477
private refetchIntervalId?: ReturnType<typeof setInterval>
7578
private currentRefetchInterval?: number | false
@@ -473,14 +476,17 @@ export class QueryObserver<
473476
if (
474477
prevResult &&
475478
state.data === prevResultState?.data &&
476-
options.select === this.previousSelectFn &&
479+
options.select === this.previousSelect?.fn &&
477480
!this.previousSelectError
478481
) {
479-
data = prevResult.data
482+
data = this.previousSelect.result
480483
} else {
481484
try {
482-
this.previousSelectFn = options.select
483485
data = options.select(state.data)
486+
this.previousSelect = {
487+
fn: options.select,
488+
result: data,
489+
}
484490
if (options.structuralSharing !== false) {
485491
data = replaceEqualDeep(prevResult?.data, data)
486492
}

src/devtools/styledComponents.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export const QueryKey = styled('span', {
6565

6666
export const Code = styled('code', {
6767
fontSize: '.9em',
68+
color: 'inherit',
6869
})
6970

7071
export const Input = styled('input', (_props, theme) => ({

src/reactjs/tests/useQuery.test.tsx

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4102,6 +4102,56 @@ describe('useQuery', () => {
41024102
expect(selectRun).toBe(3)
41034103
})
41044104

4105+
it('select should always return the correct state', async () => {
4106+
const key1 = queryKey()
4107+
4108+
function Page() {
4109+
const [count, inc] = React.useReducer(prev => prev + 1, 2)
4110+
const [forceValue, forceUpdate] = React.useReducer(prev => prev + 1, 1)
4111+
4112+
const state = useQuery(
4113+
key1,
4114+
async () => {
4115+
await sleep(10)
4116+
return 0
4117+
},
4118+
{
4119+
select: React.useCallback(
4120+
(data: number) => {
4121+
return `selected ${data + count}`
4122+
},
4123+
[count]
4124+
),
4125+
placeholderData: 99,
4126+
}
4127+
)
4128+
4129+
return (
4130+
<div>
4131+
<h2>Data: {state.data}</h2>
4132+
<h2>forceValue: {forceValue}</h2>
4133+
<button onClick={inc}>inc: {count}</button>
4134+
<button onClick={forceUpdate}>forceUpdate</button>
4135+
</div>
4136+
)
4137+
}
4138+
4139+
const rendered = renderWithClient(queryClient, <Page />)
4140+
await waitFor(() => rendered.getByText('Data: selected 101')) // 99 + 2
4141+
4142+
await waitFor(() => rendered.getByText('Data: selected 2')) // 0 + 2
4143+
4144+
rendered.getByRole('button', { name: /inc/i }).click()
4145+
4146+
await waitFor(() => rendered.getByText('Data: selected 3')) // 0 + 3
4147+
4148+
rendered.getByRole('button', { name: /forceUpdate/i }).click()
4149+
4150+
await waitFor(() => rendered.getByText('forceValue: 2'))
4151+
// data should still be 3 after an independent re-render
4152+
await waitFor(() => rendered.getByText('Data: selected 3'))
4153+
})
4154+
41054155
it('should cancel the query function when there are no more subscriptions', async () => {
41064156
const key = queryKey()
41074157
let cancelFn: jest.Mock = jest.fn()
@@ -4611,6 +4661,82 @@ describe('useQuery', () => {
46114661
consoleMock.mockRestore()
46124662
})
46134663

4664+
it('should have no error in loading state when refetching after error occurred', async () => {
4665+
const consoleMock = mockConsoleError()
4666+
const key = queryKey()
4667+
const states: UseQueryResult<number>[] = []
4668+
const error = new Error('oops')
4669+
4670+
let count = 0
4671+
4672+
function Page() {
4673+
const state = useQuery(
4674+
key,
4675+
async () => {
4676+
await sleep(10)
4677+
if (count === 0) {
4678+
count++
4679+
throw error
4680+
}
4681+
return 5
4682+
},
4683+
{
4684+
retry: false,
4685+
}
4686+
)
4687+
4688+
states.push(state)
4689+
4690+
if (state.isLoading) {
4691+
return <div>status: loading</div>
4692+
}
4693+
if (state.error instanceof Error) {
4694+
return (
4695+
<div>
4696+
<div>error</div>
4697+
<button onClick={() => state.refetch()}>refetch</button>
4698+
</div>
4699+
)
4700+
}
4701+
return <div>data: {state.data}</div>
4702+
}
4703+
4704+
const rendered = renderWithClient(queryClient, <Page />)
4705+
4706+
await waitFor(() => rendered.getByText('error'))
4707+
4708+
fireEvent.click(rendered.getByRole('button', { name: 'refetch' }))
4709+
await waitFor(() => rendered.getByText('data: 5'))
4710+
4711+
await waitFor(() => expect(states.length).toBe(4))
4712+
4713+
expect(states[0]).toMatchObject({
4714+
status: 'loading',
4715+
data: undefined,
4716+
error: null,
4717+
})
4718+
4719+
expect(states[1]).toMatchObject({
4720+
status: 'error',
4721+
data: undefined,
4722+
error,
4723+
})
4724+
4725+
expect(states[2]).toMatchObject({
4726+
status: 'loading',
4727+
data: undefined,
4728+
error: null,
4729+
})
4730+
4731+
expect(states[3]).toMatchObject({
4732+
status: 'success',
4733+
data: 5,
4734+
error: null,
4735+
})
4736+
4737+
consoleMock.mockRestore()
4738+
})
4739+
46144740
describe('networkMode online', () => {
46154741
it('online queries should not start fetching if you are offline', async () => {
46164742
const onlineMock = mockNavigatorOnLine(false)

src/reactjs/types.ts

Lines changed: 8 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
1-
import { RetryValue, RetryDelayValue } from '../core/retryer'
21
import {
32
InfiniteQueryObserverOptions,
43
InfiniteQueryObserverResult,
54
MutationObserverResult,
6-
MutationKey,
75
QueryObserverOptions,
86
QueryObserverResult,
97
QueryKey,
10-
MutationFunction,
11-
MutateOptions,
12-
MutationMeta,
13-
NetworkMode,
8+
MutationObserverOptions,
9+
MutateFunction,
1410
} from '../core/types'
1511

1612
export interface UseBaseQueryOptions<
@@ -74,55 +70,26 @@ export interface UseMutationOptions<
7470
TError = unknown,
7571
TVariables = void,
7672
TContext = unknown
77-
> {
78-
mutationFn?: MutationFunction<TData, TVariables>
79-
mutationKey?: MutationKey
80-
cacheTime?: number
81-
onMutate?: (
82-
variables: TVariables
83-
) => Promise<TContext | undefined> | TContext | undefined
84-
onSuccess?: (
85-
data: TData,
86-
variables: TVariables,
87-
context: TContext | undefined
88-
) => Promise<unknown> | void
89-
onError?: (
90-
error: TError,
91-
variables: TVariables,
92-
context: TContext | undefined
93-
) => Promise<unknown> | void
94-
onSettled?: (
95-
data: TData | undefined,
96-
error: TError | null,
97-
variables: TVariables,
98-
context: TContext | undefined
99-
) => Promise<unknown> | void
100-
retry?: RetryValue<TError>
101-
retryDelay?: RetryDelayValue<TError>
102-
networkMode?: NetworkMode
103-
useErrorBoundary?: boolean | ((error: TError) => boolean)
104-
meta?: MutationMeta
105-
}
73+
> extends Omit<
74+
MutationObserverOptions<TData, TError, TVariables, TContext>,
75+
'_defaulted' | 'variables'
76+
> {}
10677

10778
export type UseMutateFunction<
10879
TData = unknown,
10980
TError = unknown,
11081
TVariables = void,
11182
TContext = unknown
11283
> = (
113-
variables: TVariables,
114-
options?: MutateOptions<TData, TError, TVariables, TContext>
84+
...args: Parameters<MutateFunction<TData, TError, TVariables, TContext>>
11585
) => void
11686

11787
export type UseMutateAsyncFunction<
11888
TData = unknown,
11989
TError = unknown,
12090
TVariables = void,
12191
TContext = unknown
122-
> = (
123-
variables: TVariables,
124-
options?: MutateOptions<TData, TError, TVariables, TContext>
125-
) => Promise<TData>
92+
> = MutateFunction<TData, TError, TVariables, TContext>
12693

12794
export type UseBaseMutationResult<
12895
TData = unknown,

0 commit comments

Comments
 (0)