Skip to content

Commit

Permalink
Add args to forceRefetch, add doc block, and test for merging
Browse files Browse the repository at this point in the history
  • Loading branch information
markerikson committed Oct 8, 2022
1 parent 0dedba0 commit d0118bb
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 8 deletions.
31 changes: 23 additions & 8 deletions packages/toolkit/src/query/core/buildThunks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -475,36 +475,51 @@ In the case of an unhandled error, no tags will be "provided" or "invalidated".`
getPendingMeta() {
return { startedTimeStamp: Date.now() }
},
condition(arg, { getState }) {
condition(queryThunkArgs, { getState }) {
const state = getState()
const requestState = state[reducerPath]?.queries?.[arg.queryCacheKey]

const requestState =
state[reducerPath]?.queries?.[queryThunkArgs.queryCacheKey]
const fulfilledVal = requestState?.fulfilledTimeStamp
const endpointDefinition = endpointDefinitions[arg.endpointName]
const currentArg = queryThunkArgs.originalArgs
const previousArg = requestState?.originalArgs
const endpointDefinition =
endpointDefinitions[queryThunkArgs.endpointName]

// Order of these checks matters.
// In order for `upsertQueryData` to successfully run while an existing request is in flight,
/// we have to check for that first, otherwise `queryThunk` will bail out and not run at all.
if (isUpsertQuery(arg)) return true
if (isUpsertQuery(queryThunkArgs)) {
return true
}

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

// if this is forced, continue
if (isForcedQuery(arg, state)) return true
if (isForcedQuery(queryThunkArgs, state)) {
return true
}

if (
isQueryDefinition(endpointDefinition) &&
endpointDefinition?.forceRefetch?.({
currentArg,
previousArg,
endpointState: requestState,
state,
})
)
) {
return true
}

// Pull from the cache unless we explicitly force refetch or qualify based on time
if (fulfilledVal)
if (fulfilledVal) {
// Value is cached and we didn't specify to refresh, skip it.
return false
}

return true
},
Expand Down
25 changes: 25 additions & 0 deletions packages/toolkit/src/query/endpointDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,32 @@ export interface QueryExtraOptions<
responseData: ResultType
): ResultType | void

/**
* Check to see if the endpoint should force a refetch in cases where it normally wouldn't.
* This is primarily useful for "infinite scroll" / pagination use cases where
* RTKQ is keeping a single cache entry that is added to over time, in combination
* with `serializeQueryArgs` returning a fixed cache key and a `merge` callback
* set to add incoming data to the cache entry each time.
*
* Example:
*
* ```ts
* forceRefetch({currentArg, previousArg}) {
* // Assume these are page numbers
* return currentArg !== previousArg
* },
* serializeQueryArgs({endpointName}) {
* return endpointName
* },
* merge(currentCacheData, responseData) {
* currentCacheData.push(...responseData)
* }
*
* ```
*/
forceRefetch?(params: {
currentArg: QueryArg | undefined
previousArg: QueryArg | undefined
state: RootState<any, any, string>
endpointState?: QuerySubState<any>
}): boolean
Expand Down
47 changes: 47 additions & 0 deletions packages/toolkit/src/query/tests/createApi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -866,6 +866,18 @@ describe('custom serializeQueryArgs per endpoint', () => {
query: (arg) => `${arg}`,
serializeQueryArgs: serializer1,
}),
listItems: build.query<string[], number>({
query: (pageNumber) => `/listItems?page=${pageNumber}`,
serializeQueryArgs: ({ endpointName }) => {
return endpointName
},
merge: (currentCache, newItems) => {
currentCache.push(...newItems)
},
forceRefetch({ currentArg, previousArg }) {
return currentArg !== previousArg
},
}),
}),
})

Expand Down Expand Up @@ -918,4 +930,39 @@ describe('custom serializeQueryArgs per endpoint', () => {
]
).toBeTruthy()
})

test('serializeQueryArgs + merge allows refetching as args change with same cache key', async () => {
const allItems = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'i']
const PAGE_SIZE = 3

function paginate<T>(array: T[], page_size: number, page_number: number) {
// human-readable page numbers usually start with 1, so we reduce 1 in the first argument
return array.slice((page_number - 1) * page_size, page_number * page_size)
}

server.use(
rest.get('https://example.com/listItems', (req, res, ctx) => {
const pageString = req.url.searchParams.get('page')
console.log('Page string: ', pageString)
const pageNum = parseInt(pageString || '0')

const results = paginate(allItems, PAGE_SIZE, pageNum)
console.log('Page num: ', pageNum, 'Results: ', results)
return res(ctx.json(results))
})
)

// Page number shouldn't matter here, because the cache key ignores that.
// We just need to select the only cache entry.
const selectListItems = api.endpoints.listItems.select(0)

await storeRef.store.dispatch(api.endpoints.listItems.initiate(1))

const initialEntry = selectListItems(storeRef.store.getState())
expect(initialEntry.data).toEqual(['a', 'b', 'c'])

await storeRef.store.dispatch(api.endpoints.listItems.initiate(2))
const updatedEntry = selectListItems(storeRef.store.getState())
expect(updatedEntry.data).toEqual(['a', 'b', 'c', 'd', 'e', 'f'])
})
})

0 comments on commit d0118bb

Please sign in to comment.