Skip to content

Commit ae94709

Browse files
phryneasmsutkowski
andauthored
add endpoint, type and forced to BaseQueryApi and prepareHeaders (#1656)
Co-authored-by: Matt Sutkowski <msutkowski@gmail.com>
1 parent 199ad89 commit ae94709

File tree

8 files changed

+171
-266
lines changed

8 files changed

+171
-266
lines changed

packages/toolkit/src/query/baseQueryTypes.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,17 @@ export interface BaseQueryApi {
66
dispatch: ThunkDispatch<any, any, any>
77
getState: () => unknown
88
extra: unknown
9+
endpoint: string
10+
type: 'query' | 'mutation'
11+
/**
12+
* Only available for queries: indicates if a query has been forced,
13+
* i.e. it would have been fetched even if there would already be a cache entry
14+
* (this does not mean that there is already a cache entry though!)
15+
*
16+
* This can be used to for example add a `Cache-Control: no-cache` header for
17+
* invalidated queries.
18+
*/
19+
forced?: boolean
920
}
1021

1122
export type QueryReturnValue<T = unknown, E = unknown, M = unknown> =

packages/toolkit/src/query/core/buildInitiate.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ Features like automatic cache collection, automatic refetching etc. will not be
263263
endpointName,
264264
})
265265
const thunk = queryThunk({
266+
type: 'query',
266267
subscribe,
267268
forceRefetch,
268269
subscriptionOptions,
@@ -330,6 +331,7 @@ Features like automatic cache collection, automatic refetching etc. will not be
330331
return (arg, { track = true, fixedCacheKey } = {}) =>
331332
(dispatch, getState) => {
332333
const thunk = mutationThunk({
334+
type: 'mutation',
333335
endpointName,
334336
originalArgs: arg,
335337
track,

packages/toolkit/src/query/core/buildMiddleware/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export function buildMiddleware<
7575
override: Partial<QueryThunkArg> = {}
7676
) {
7777
return queryThunk({
78+
type: 'query',
7879
endpointName: querySubState.endpointName,
7980
originalArgs: querySubState.originalArgs,
8081
subscribe: false,

packages/toolkit/src/query/core/buildThunks.ts

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,13 @@ export interface Matchers<
103103
export interface QueryThunkArg
104104
extends QuerySubstateIdentifier,
105105
StartQueryActionCreatorOptions {
106+
type: 'query'
106107
originalArgs: unknown
107108
endpointName: string
108109
}
109110

110111
export interface MutationThunkArg {
112+
type: 'mutation'
111113
originalArgs: unknown
112114
endpointName: string
113115
track?: boolean
@@ -276,6 +278,10 @@ export function buildThunks<
276278
dispatch,
277279
getState,
278280
extra,
281+
endpoint: arg.endpointName,
282+
type: arg.type,
283+
forced:
284+
arg.type === 'query' ? isForcedQuery(arg, getState()) : undefined,
279285
}
280286
if (endpointDefinition.query) {
281287
result = await baseQuery(
@@ -325,6 +331,28 @@ In the case of an unhandled error, no tags will be "provided" or "invalidated".`
325331
}
326332
}
327333

334+
function isForcedQuery(
335+
arg: QueryThunkArg,
336+
state: RootState<any, string, ReducerPath>
337+
) {
338+
const requestState = state[reducerPath]?.queries?.[arg.queryCacheKey]
339+
const baseFetchOnMountOrArgChange =
340+
state[reducerPath]?.config.refetchOnMountOrArgChange
341+
342+
const fulfilledVal = requestState?.fulfilledTimeStamp
343+
const refetchVal =
344+
arg.forceRefetch ?? (arg.subscribe && baseFetchOnMountOrArgChange)
345+
346+
if (refetchVal) {
347+
// Return if its true or compare the dates because it must be a number
348+
return (
349+
refetchVal === true ||
350+
(Number(new Date()) - Number(fulfilledVal)) / 1000 >= refetchVal
351+
)
352+
}
353+
return false
354+
}
355+
328356
const queryThunk = createAsyncThunk<
329357
ThunkResult,
330358
QueryThunkArg,
@@ -334,29 +362,20 @@ In the case of an unhandled error, no tags will be "provided" or "invalidated".`
334362
return { startedTimeStamp: Date.now() }
335363
},
336364
condition(arg, { getState }) {
337-
const state = getState()[reducerPath]
338-
const requestState = state?.queries?.[arg.queryCacheKey]
339-
const baseFetchOnMountOrArgChange = state.config.refetchOnMountOrArgChange
340-
365+
const state = getState()
366+
const requestState = state[reducerPath]?.queries?.[arg.queryCacheKey]
341367
const fulfilledVal = requestState?.fulfilledTimeStamp
342-
const refetchVal =
343-
arg.forceRefetch ?? (arg.subscribe && baseFetchOnMountOrArgChange)
344368

345369
// Don't retry a request that's currently in-flight
346370
if (requestState?.status === 'pending') return false
347371

372+
// if this is forced, continue
373+
if (isForcedQuery(arg, state)) return true
374+
348375
// Pull from the cache unless we explicitly force refetch or qualify based on time
349-
if (fulfilledVal) {
350-
if (refetchVal) {
351-
// Return if its true or compare the dates because it must be a number
352-
return (
353-
refetchVal === true ||
354-
(Number(new Date()) - Number(fulfilledVal)) / 1000 >= refetchVal
355-
)
356-
}
376+
if (fulfilledVal)
357377
// Value is cached and we didn't specify to refresh, skip it.
358378
return false
359-
}
360379

361380
return true
362381
},

packages/toolkit/src/query/fetchBaseQuery.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { joinUrls } from './utils'
22
import { isPlainObject } from '@reduxjs/toolkit'
3-
import type { BaseQueryFn } from './baseQueryTypes'
3+
import type { BaseQueryApi, BaseQueryFn } from './baseQueryTypes'
44
import type { MaybePromise, Override } from './tsHelpers'
55

66
export type ResponseHandler =
@@ -113,7 +113,7 @@ export type FetchBaseQueryArgs = {
113113
baseUrl?: string
114114
prepareHeaders?: (
115115
headers: Headers,
116-
api: { getState: () => unknown }
116+
api: Pick<BaseQueryApi, 'getState' | 'endpoint' | 'type' | 'forced'>
117117
) => MaybePromise<Headers>
118118
fetchFn?: (
119119
input: RequestInfo,
@@ -178,7 +178,7 @@ export function fetchBaseQuery({
178178
'Warning: `fetch` is not available. Please supply a custom `fetchFn` property to use `fetchBaseQuery` on SSR environments.'
179179
)
180180
}
181-
return async (arg, { signal, getState }) => {
181+
return async (arg, { signal, getState, endpoint, forced, type }) => {
182182
let meta: FetchBaseQueryMeta | undefined
183183
let {
184184
url,
@@ -200,7 +200,7 @@ export function fetchBaseQuery({
200200

201201
config.headers = await prepareHeaders(
202202
new Headers(stripUndefined(headers)),
203-
{ getState }
203+
{ getState, endpoint, forced, type }
204204
)
205205

206206
// Only set the content-type to json if appropriate. Will not be true for FormData, ArrayBuffer, Blob, etc.

packages/toolkit/src/query/tests/createApi.test.ts

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -305,10 +305,14 @@ describe('endpoint definition typings', () => {
305305

306306
describe('enhancing endpoint definitions', () => {
307307
const baseQuery = jest.fn((x: string) => ({ data: 'success' }))
308-
const baseQueryApiMatcher = {
308+
const commonBaseQueryApi = {
309309
dispatch: expect.any(Function),
310+
endpoint: expect.any(String),
311+
extra: undefined,
312+
forced: expect.any(Boolean),
310313
getState: expect.any(Function),
311314
signal: expect.any(Object),
315+
type: expect.any(String),
312316
}
313317
beforeEach(() => {
314318
baseQuery.mockClear()
@@ -337,11 +341,56 @@ describe('endpoint definition typings', () => {
337341
storeRef.store.dispatch(api.endpoints.query2.initiate('in2'))
338342
storeRef.store.dispatch(api.endpoints.mutation1.initiate('in1'))
339343
storeRef.store.dispatch(api.endpoints.mutation2.initiate('in2'))
344+
340345
expect(baseQuery.mock.calls).toEqual([
341-
['in1', baseQueryApiMatcher, undefined],
342-
['in2', baseQueryApiMatcher, undefined],
343-
['in1', baseQueryApiMatcher, undefined],
344-
['in2', baseQueryApiMatcher, undefined],
346+
[
347+
'in1',
348+
{
349+
dispatch: expect.any(Function),
350+
endpoint: expect.any(String),
351+
getState: expect.any(Function),
352+
signal: expect.any(Object),
353+
forced: expect.any(Boolean),
354+
type: expect.any(String),
355+
},
356+
undefined,
357+
],
358+
[
359+
'in2',
360+
{
361+
dispatch: expect.any(Function),
362+
endpoint: expect.any(String),
363+
getState: expect.any(Function),
364+
signal: expect.any(Object),
365+
forced: expect.any(Boolean),
366+
type: expect.any(String),
367+
},
368+
undefined,
369+
],
370+
[
371+
'in1',
372+
{
373+
dispatch: expect.any(Function),
374+
endpoint: expect.any(String),
375+
getState: expect.any(Function),
376+
signal: expect.any(Object),
377+
// forced: undefined,
378+
type: expect.any(String),
379+
},
380+
undefined,
381+
],
382+
[
383+
'in2',
384+
{
385+
dispatch: expect.any(Function),
386+
endpoint: expect.any(String),
387+
getState: expect.any(Function),
388+
signal: expect.any(Object),
389+
// forced: undefined,
390+
type: expect.any(String),
391+
},
392+
undefined,
393+
],
345394
])
346395
})
347396

@@ -439,11 +488,12 @@ describe('endpoint definition typings', () => {
439488
storeRef.store.dispatch(api.endpoints.query2.initiate('in2'))
440489
storeRef.store.dispatch(api.endpoints.mutation1.initiate('in1'))
441490
storeRef.store.dispatch(api.endpoints.mutation2.initiate('in2'))
491+
442492
expect(baseQuery.mock.calls).toEqual([
443-
['modified1', baseQueryApiMatcher, undefined],
444-
['modified2', baseQueryApiMatcher, undefined],
445-
['modified1', baseQueryApiMatcher, undefined],
446-
['modified2', baseQueryApiMatcher, undefined],
493+
['modified1', commonBaseQueryApi, undefined],
494+
['modified2', commonBaseQueryApi, undefined],
495+
['modified1', { ...commonBaseQueryApi, forced: undefined }, undefined],
496+
['modified2', { ...commonBaseQueryApi, forced: undefined }, undefined],
447497
])
448498
})
449499
})

packages/toolkit/src/query/tests/errorHandling.test.tsx

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { server } from './mocks/server'
1111
import { fireEvent, render, waitFor, screen } from '@testing-library/react'
1212
import { useDispatch } from 'react-redux'
1313
import type { AnyAction, ThunkDispatch } from '@reduxjs/toolkit'
14+
import type { BaseQueryApi } from '../baseQueryTypes'
1415

1516
const baseQuery = fetchBaseQuery({ baseUrl: 'http://example.com' })
1617

@@ -33,18 +34,20 @@ const failQueryOnce = rest.get('/query', (_, req, ctx) =>
3334
)
3435

3536
describe('fetchBaseQuery', () => {
37+
let commonBaseQueryApiArgs: BaseQueryApi = {} as any
38+
beforeEach(() => {
39+
commonBaseQueryApiArgs = {
40+
signal: new AbortController().signal,
41+
dispatch: storeRef.store.dispatch,
42+
getState: storeRef.store.getState,
43+
extra: undefined,
44+
type: 'query',
45+
endpoint: 'doesntmatterhere',
46+
}
47+
})
3648
test('success', async () => {
3749
await expect(
38-
baseQuery(
39-
'/success',
40-
{
41-
signal: new AbortController().signal,
42-
dispatch: storeRef.store.dispatch,
43-
getState: storeRef.store.getState,
44-
extra: undefined,
45-
},
46-
{}
47-
)
50+
baseQuery('/success', commonBaseQueryApiArgs, {})
4851
).resolves.toEqual({
4952
data: { value: 'success' },
5053
meta: {
@@ -56,16 +59,7 @@ describe('fetchBaseQuery', () => {
5659
test('error', async () => {
5760
server.use(failQueryOnce)
5861
await expect(
59-
baseQuery(
60-
'/error',
61-
{
62-
signal: new AbortController().signal,
63-
dispatch: storeRef.store.dispatch,
64-
getState: storeRef.store.getState,
65-
extra: undefined,
66-
},
67-
{}
68-
)
62+
baseQuery('/error', commonBaseQueryApiArgs, {})
6963
).resolves.toEqual({
7064
error: {
7165
data: { value: 'error' },

0 commit comments

Comments
 (0)