Skip to content

Commit a7ceaa2

Browse files
authored
Merge pull request #2965 from reduxjs/feature/1.9.1-more-fixes
2 parents 87bebec + eaf7d5e commit a7ceaa2

File tree

5 files changed

+95
-19
lines changed

5 files changed

+95
-19
lines changed

packages/toolkit/src/createAsyncThunk.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -590,18 +590,10 @@ If you want to use the AbortController to react to \`abort\` events, please cons
590590
const abortController = new AC()
591591
let abortReason: string | undefined
592592

593-
const abortedPromise = new Promise<never>((_, reject) =>
594-
abortController.signal.addEventListener('abort', () =>
595-
reject({ name: 'AbortError', message: abortReason || 'Aborted' })
596-
)
597-
)
598-
599593
let started = false
600594
function abort(reason?: string) {
601-
if (started) {
602-
abortReason = reason
603-
abortController.abort()
604-
}
595+
abortReason = reason
596+
abortController.abort()
605597
}
606598

607599
const promise = (async function () {
@@ -611,14 +603,24 @@ If you want to use the AbortController to react to \`abort\` events, please cons
611603
if (isThenable(conditionResult)) {
612604
conditionResult = await conditionResult
613605
}
614-
if (conditionResult === false) {
606+
607+
if (conditionResult === false || abortController.signal.aborted) {
615608
// eslint-disable-next-line no-throw-literal
616609
throw {
617610
name: 'ConditionError',
618611
message: 'Aborted due to condition callback returning false.',
619612
}
620613
}
621614
started = true
615+
616+
const abortedPromise = new Promise<never>((_, reject) =>
617+
abortController.signal.addEventListener('abort', () =>
618+
reject({
619+
name: 'AbortError',
620+
message: abortReason || 'Aborted',
621+
})
622+
)
623+
)
622624
dispatch(
623625
pending(
624626
requestId,

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,8 @@ export function buildSlice({
181181

182182
if (merge) {
183183
if (substate.data !== undefined) {
184+
const { fulfilledTimeStamp, arg, baseQueryMeta, requestId } =
185+
meta
184186
// There's existing cache data. Let the user merge it in themselves.
185187
// We're already inside an Immer-powered reducer, and the user could just mutate `substate.data`
186188
// themselves inside of `merge()`. But, they might also want to return a new value.
@@ -189,7 +191,12 @@ export function buildSlice({
189191
substate.data,
190192
(draftSubstateData) => {
191193
// As usual with Immer, you can mutate _or_ return inside here, but not both
192-
return merge(draftSubstateData, payload)
194+
return merge(draftSubstateData, payload, {
195+
arg: arg.originalArgs,
196+
baseQueryMeta,
197+
fulfilledTimeStamp,
198+
requestId,
199+
})
193200
}
194201
)
195202
substate.data = newData

packages/toolkit/src/query/endpointDefinitions.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,13 @@ export interface QueryExtraOptions<
445445
*/
446446
merge?(
447447
currentCacheData: ResultType,
448-
responseData: ResultType
448+
responseData: ResultType,
449+
otherArgs: {
450+
arg: QueryArg
451+
baseQueryMeta: BaseQueryMeta<BaseQuery>
452+
requestId: string
453+
fulfilledTimeStamp: number
454+
}
449455
): ResultType | void
450456

451457
/**

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

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ afterAll(() => {
3535
spy.mockRestore()
3636
})
3737

38+
function paginate<T>(array: T[], page_size: number, page_number: number) {
39+
// human-readable page numbers usually start with 1, so we reduce 1 in the first argument
40+
return array.slice((page_number - 1) * page_size, page_number * page_size)
41+
}
42+
3843
test('sensible defaults', () => {
3944
const api = createApi({
4045
baseQuery: fetchBaseQuery(),
@@ -923,6 +928,22 @@ describe('custom serializeQueryArgs per endpoint', () => {
923928
return currentArg !== previousArg
924929
},
925930
}),
931+
listItems2: build.query<{ items: string[]; meta?: any }, number>({
932+
query: (pageNumber) => `/listItems2?page=${pageNumber}`,
933+
serializeQueryArgs: ({ endpointName }) => {
934+
return endpointName
935+
},
936+
transformResponse(items: string[]) {
937+
return { items }
938+
},
939+
merge: (currentCache, newData, meta) => {
940+
currentCache.items.push(...newData.items)
941+
currentCache.meta = meta
942+
},
943+
forceRefetch({ currentArg, previousArg }) {
944+
return currentArg !== previousArg
945+
},
946+
}),
926947
}),
927948
})
928949

@@ -1010,11 +1031,6 @@ describe('custom serializeQueryArgs per endpoint', () => {
10101031
const allItems = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'i']
10111032
const PAGE_SIZE = 3
10121033

1013-
function paginate<T>(array: T[], page_size: number, page_number: number) {
1014-
// human-readable page numbers usually start with 1, so we reduce 1 in the first argument
1015-
return array.slice((page_number - 1) * page_size, page_number * page_size)
1016-
}
1017-
10181034
server.use(
10191035
rest.get('https://example.com/listItems', (req, res, ctx) => {
10201036
const pageString = req.url.searchParams.get('page')
@@ -1038,4 +1054,33 @@ describe('custom serializeQueryArgs per endpoint', () => {
10381054
const updatedEntry = selectListItems(storeRef.store.getState())
10391055
expect(updatedEntry.data).toEqual(['a', 'b', 'c', 'd', 'e', 'f'])
10401056
})
1057+
1058+
test('merge receives a meta object as an argument', async () => {
1059+
const allItems = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'i']
1060+
const PAGE_SIZE = 3
1061+
1062+
server.use(
1063+
rest.get('https://example.com/listItems2', (req, res, ctx) => {
1064+
const pageString = req.url.searchParams.get('page')
1065+
const pageNum = parseInt(pageString || '0')
1066+
1067+
const results = paginate(allItems, PAGE_SIZE, pageNum)
1068+
return res(ctx.json(results))
1069+
})
1070+
)
1071+
1072+
const selectListItems = api.endpoints.listItems2.select(0)
1073+
1074+
await storeRef.store.dispatch(api.endpoints.listItems2.initiate(1))
1075+
await storeRef.store.dispatch(api.endpoints.listItems2.initiate(2))
1076+
const cacheEntry = selectListItems(storeRef.store.getState())
1077+
1078+
// Should have passed along the third arg from `merge` containing these fields
1079+
expect(cacheEntry.data?.meta).toEqual({
1080+
requestId: expect.any(String),
1081+
fulfilledTimeStamp: expect.any(Number),
1082+
arg: 2,
1083+
baseQueryMeta: expect.any(Object),
1084+
})
1085+
})
10411086
})

packages/toolkit/src/tests/createAsyncThunk.test.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
getLog,
1414
} from 'console-testing-library/pure'
1515
import { expectType } from './helpers'
16+
import { delay } from '../utils'
1617

1718
declare global {
1819
interface Window {
@@ -621,6 +622,21 @@ describe('conditional skipping of asyncThunks', () => {
621622
)
622623
})
623624

625+
test('async condition with AbortController signal first', async () => {
626+
const condition = async () => {
627+
await delay(25)
628+
return true
629+
}
630+
const asyncThunk = createAsyncThunk('test', payloadCreator, { condition })
631+
632+
try {
633+
const thunkPromise = asyncThunk(arg)(dispatch, getState, extra)
634+
thunkPromise.abort()
635+
await thunkPromise
636+
} catch (err) {}
637+
expect(dispatch).toHaveBeenCalledTimes(0)
638+
})
639+
624640
test('rejected action is not dispatched by default', async () => {
625641
const asyncThunk = createAsyncThunk('test', payloadCreator, { condition })
626642
await asyncThunk(arg)(dispatch, getState, extra)
@@ -630,7 +646,7 @@ describe('conditional skipping of asyncThunks', () => {
630646

631647
test('does not fail when attempting to abort a canceled promise', async () => {
632648
const asyncPayloadCreator = jest.fn(async (x: typeof arg) => {
633-
await new Promise((resolve) => setTimeout(resolve, 2000))
649+
await delay(200)
634650
return 10
635651
})
636652

0 commit comments

Comments
 (0)