Skip to content

Commit ada1f4b

Browse files
authored
Merge pull request #1084 from reduxjs/feature/thunk-shape
2 parents 80daf2f + dcd4c8d commit ada1f4b

20 files changed

+305
-123
lines changed

src/createAsyncThunk.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,9 @@ export type AsyncThunkRejectedActionCreator<
366366
aborted: boolean
367367
condition: boolean
368368
} & (
369-
| { rejectedWithValue: false }
369+
| ({ rejectedWithValue: false } & {
370+
[K in keyof GetRejectedMeta<ThunkApiConfig>]?: undefined
371+
})
370372
| ({ rejectedWithValue: true } & GetRejectedMeta<ThunkApiConfig>)
371373
)
372374
>

src/query/baseQueryTypes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export type BaseQueryMeta<BaseQuery extends BaseQueryFn> = UnwrapPromise<
5959

6060
export type BaseQueryError<BaseQuery extends BaseQueryFn> = Exclude<
6161
UnwrapPromise<ReturnType<BaseQuery>>,
62-
{ error: undefined }
62+
{ error?: undefined }
6363
>['error']
6464

6565
export type BaseQueryArg<

src/query/core/buildInitiate.ts

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@ import type {
55
QueryArgFrom,
66
ResultTypeFrom,
77
} from '../endpointDefinitions'
8-
import type { QueryThunkArg, MutationThunkArg } from './buildThunks'
8+
import type {
9+
QueryThunkArg,
10+
MutationThunkArg,
11+
QueryThunk,
12+
MutationThunk,
13+
} from './buildThunks'
914
import type {
1015
AnyAction,
1116
AsyncThunk,
@@ -108,10 +113,6 @@ export type MutationActionCreatorResult<
108113
* Whether the mutation is being tracked in the store.
109114
*/
110115
track?: boolean
111-
/**
112-
* Timestamp for when the mutation was initiated
113-
*/
114-
startedTimeStamp: number
115116
}
116117
/**
117118
* A unique string generated for the request sequence
@@ -186,8 +187,8 @@ export function buildInitiate({
186187
api,
187188
}: {
188189
serializeQueryArgs: InternalSerializeQueryArgs
189-
queryThunk: AsyncThunk<any, QueryThunkArg, {}>
190-
mutationThunk: AsyncThunk<any, MutationThunkArg, {}>
190+
queryThunk: QueryThunk
191+
mutationThunk: MutationThunk
191192
api: Api<any, EndpointDefinitions, any, any>
192193
}) {
193194
const {
@@ -234,7 +235,6 @@ Features like automatic cache collection, automatic refetching etc. will not be
234235
endpointName,
235236
originalArgs: arg,
236237
queryCacheKey,
237-
startedTimeStamp: Date.now(),
238238
})
239239
const thunkResult = dispatch(thunk)
240240
middlewareWarning(getState)
@@ -289,26 +289,19 @@ Features like automatic cache collection, automatic refetching etc. will not be
289289
endpointName,
290290
originalArgs: arg,
291291
track,
292-
startedTimeStamp: Date.now(),
293292
})
294293
const thunkResult = dispatch(thunk)
295294
middlewareWarning(getState)
296295
const { requestId, abort } = thunkResult
297296
const returnValuePromise = thunkResult
298-
.then(unwrapResult)
299-
.then((unwrapped) => ({
300-
data: unwrapped.result,
301-
}))
297+
.unwrap()
298+
.then((data) => ({ data }))
302299
.catch((error) => ({ error }))
303300
return Object.assign(returnValuePromise, {
304301
arg: thunkResult.arg,
305302
requestId,
306303
abort,
307-
unwrap() {
308-
return thunkResult
309-
.then(unwrapResult)
310-
.then((unwrapped) => unwrapped.result)
311-
},
304+
unwrap: thunkResult.unwrap,
312305
unsubscribe() {
313306
if (track) dispatch(unsubscribeMutationResult({ requestId }))
314307
},

src/query/core/buildMiddleware/cacheLifecycle.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { isAsyncThunkAction, isFulfilled } from '@reduxjs/toolkit'
22
import type { AnyAction } from 'redux'
33
import type { ThunkDispatch } from 'redux-thunk'
4-
import type { BaseQueryFn } from '../../baseQueryTypes'
4+
import type { BaseQueryFn, BaseQueryMeta } from '../../baseQueryTypes'
55
import { DefinitionType } from '../../endpointDefinitions'
66
import type { RootState } from '../apiState'
77
import type {
@@ -78,7 +78,10 @@ declare module '../../endpointDefinitions' {
7878
requestId: string
7979
}
8080

81-
export interface CacheLifecyclePromises<ResultType = unknown> {
81+
export interface CacheLifecyclePromises<
82+
ResultType = unknown,
83+
MetaType = unknown
84+
> {
8285
/**
8386
* Promise that will resolve with the first value for this cache key.
8487
* This allows you to `await` until an actual value is in cache.
@@ -98,6 +101,10 @@ declare module '../../endpointDefinitions' {
98101
* The (transformed) query result.
99102
*/
100103
data: ResultType
104+
/**
105+
* The `meta` returned by the `baseQuery`
106+
*/
107+
meta: MetaType
101108
},
102109
typeof neverResolvedError
103110
>
@@ -115,7 +122,7 @@ declare module '../../endpointDefinitions' {
115122
ResultType,
116123
ReducerPath extends string = string
117124
> extends QueryBaseLifecycleApi<QueryArg, BaseQuery, ResultType, ReducerPath>,
118-
CacheLifecyclePromises<ResultType> {}
125+
CacheLifecyclePromises<ResultType, BaseQueryMeta<BaseQuery>> {}
119126

120127
export interface MutationCacheLifecycleApi<
121128
QueryArg,
@@ -128,7 +135,7 @@ declare module '../../endpointDefinitions' {
128135
ResultType,
129136
ReducerPath
130137
>,
131-
CacheLifecyclePromises<ResultType> {}
138+
CacheLifecyclePromises<ResultType, BaseQueryMeta<BaseQuery>> {}
132139

133140
interface QueryExtraOptions<
134141
TagTypes extends string,
@@ -181,7 +188,7 @@ export const build: SubMiddlewareBuilder = ({
181188

182189
return (mwApi) => {
183190
type CacheLifecycle = {
184-
valueResolved?(value: { data: unknown }): unknown
191+
valueResolved?(value: { data: unknown; meta: unknown }): unknown
185192
cacheEntryRemoved(): void
186193
}
187194
const lifecycleMap: Record<string, CacheLifecycle> = {}
@@ -219,7 +226,10 @@ export const build: SubMiddlewareBuilder = ({
219226
} else if (isFullfilledThunk(action)) {
220227
const lifecycle = lifecycleMap[cacheKey]
221228
if (lifecycle?.valueResolved) {
222-
lifecycle.valueResolved({ data: action.payload.result })
229+
lifecycle.valueResolved({
230+
data: action.payload,
231+
meta: action.meta.baseQueryMeta,
232+
})
223233
delete lifecycle.valueResolved
224234
}
225235
} else if (
@@ -268,10 +278,10 @@ export const build: SubMiddlewareBuilder = ({
268278
lifecycle.cacheEntryRemoved = resolve
269279
})
270280
const cacheDataLoaded: PromiseWithKnownReason<
271-
{ data: unknown },
281+
{ data: unknown; meta: unknown },
272282
typeof neverResolvedError
273283
> = Promise.race([
274-
new Promise<{ data: unknown }>((resolve) => {
284+
new Promise<{ data: unknown; meta: unknown }>((resolve) => {
275285
lifecycle.valueResolved = resolve
276286
}),
277287
cacheEntryRemoved.then(() => {

src/query/core/buildMiddleware/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ export function buildMiddleware<
7979
originalArgs: querySubState.originalArgs,
8080
subscribe: false,
8181
forceRefetch: true,
82-
startedTimeStamp: Date.now(),
8382
queryCacheKey: queryCacheKey as any,
8483
...override,
8584
})

src/query/core/buildMiddleware/queryLifecycle.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { isPending, isRejected, isFulfilled } from '@reduxjs/toolkit'
2-
import type { BaseQueryError, BaseQueryFn } from '../../baseQueryTypes'
2+
import type {
3+
BaseQueryError,
4+
BaseQueryFn,
5+
BaseQueryMeta,
6+
} from '../../baseQueryTypes'
37
import { DefinitionType } from '../../endpointDefinitions'
48
import type { QueryFulfilledRejectionReason } from '../../endpointDefinitions'
59
import type { Recipe } from '../buildThunks'
@@ -31,6 +35,10 @@ declare module '../../endpointDefinitions' {
3135
* The (transformed) query result.
3236
*/
3337
data: ResultType
38+
/**
39+
* The `meta` returned by the `baseQuery`
40+
*/
41+
meta: BaseQueryMeta<BaseQuery>
3442
},
3543
QueryFulfilledRejectionReason<BaseQuery>
3644
>
@@ -43,9 +51,14 @@ declare module '../../endpointDefinitions' {
4351
* If this is `false`, that means this error was returned from the `baseQuery` or `queryFn` in a controlled manner.
4452
*/
4553
isUnhandledError: false
54+
/**
55+
* The `meta` returned by the `baseQuery`
56+
*/
57+
meta: BaseQueryMeta<BaseQuery>
4658
}
4759
| {
4860
error: unknown
61+
meta?: undefined
4962
/**
5063
* If this is `true`, that means that this error is the result of `baseQueryFn`, `queryFn` or `transformResponse` throwing an error instead of handling it properly.
5164
* There can not be made any assumption about the shape of `error`.
@@ -113,8 +126,8 @@ export const build: SubMiddlewareBuilder = ({
113126

114127
return (mwApi) => {
115128
type CacheLifecycle = {
116-
resolve(value: { data: unknown }): unknown
117-
reject(value: { error: unknown; isUnhandledError: boolean }): unknown
129+
resolve(value: { data: unknown; meta: unknown }): unknown
130+
reject(value: QueryFulfilledRejectionReason<any>): unknown
118131
}
119132
const lifecycleMap: Record<string, CacheLifecycle> = {}
120133

@@ -131,7 +144,7 @@ export const build: SubMiddlewareBuilder = ({
131144
if (onQueryStarted) {
132145
const lifecycle = {} as CacheLifecycle
133146
const queryFulfilled = new (Promise as PromiseConstructorWithKnownReason)<
134-
{ data: unknown },
147+
{ data: unknown; meta: unknown },
135148
QueryFulfilledRejectionReason<any>
136149
>((resolve, reject) => {
137150
lifecycle.resolve = resolve
@@ -168,14 +181,18 @@ export const build: SubMiddlewareBuilder = ({
168181
onQueryStarted(originalArgs, lifecycleApi)
169182
}
170183
} else if (isFullfilledThunk(action)) {
171-
const { requestId } = action.meta
172-
lifecycleMap[requestId]?.resolve({ data: action.payload.result })
184+
const { requestId, baseQueryMeta } = action.meta
185+
lifecycleMap[requestId]?.resolve({
186+
data: action.payload,
187+
meta: baseQueryMeta,
188+
})
173189
delete lifecycleMap[requestId]
174190
} else if (isRejectedThunk(action)) {
175-
const { requestId, rejectedWithValue } = action.meta
191+
const { requestId, rejectedWithValue, baseQueryMeta } = action.meta
176192
lifecycleMap[requestId]?.reject({
177193
error: action.payload ?? action.error,
178194
isUnhandledError: !rejectedWithValue,
195+
meta: baseQueryMeta as any,
179196
})
180197
delete lifecycleMap[requestId]
181198
}

src/query/core/buildMiddleware/types.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,17 @@ import type {
88
} from '@reduxjs/toolkit'
99

1010
import type { Api, ApiContext } from '../../apiTypes'
11-
import type { AssertTagTypes, EndpointDefinitions } from '../../endpointDefinitions'
11+
import type {
12+
AssertTagTypes,
13+
EndpointDefinitions,
14+
} from '../../endpointDefinitions'
1215
import type { QueryStatus, QuerySubState, RootState } from '../apiState'
13-
import type { MutationThunkArg, QueryThunkArg, ThunkResult } from '../buildThunks'
16+
import type {
17+
MutationThunk,
18+
QueryThunk,
19+
QueryThunkArg,
20+
ThunkResult,
21+
} from '../buildThunks'
1422

1523
export type QueryStateMeta<T> = Record<string, undefined | T>
1624
export type TimeoutId = ReturnType<typeof setTimeout>
@@ -22,8 +30,8 @@ export interface BuildMiddlewareInput<
2230
> {
2331
reducerPath: ReducerPath
2432
context: ApiContext<Definitions>
25-
queryThunk: AsyncThunk<ThunkResult, QueryThunkArg, {}>
26-
mutationThunk: AsyncThunk<ThunkResult, MutationThunkArg, {}>
33+
queryThunk: QueryThunk
34+
mutationThunk: MutationThunk
2735
api: Api<any, Definitions, ReducerPath, TagTypes>
2836
assertTagType: AssertTagTypes
2937
}

src/query/core/buildSlice.ts

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ import type {
2929
} from './apiState'
3030
import { QueryStatus } from './apiState'
3131
import type {
32+
MutationThunk,
3233
MutationThunkArg,
34+
QueryThunk,
3335
QueryThunkArg,
3436
ThunkResult,
3537
} from './buildThunks'
@@ -81,8 +83,8 @@ export function buildSlice({
8183
config,
8284
}: {
8385
reducerPath: string
84-
queryThunk: AsyncThunk<ThunkResult, QueryThunkArg, {}>
85-
mutationThunk: AsyncThunk<ThunkResult, MutationThunkArg, {}>
86+
queryThunk: QueryThunk
87+
mutationThunk: MutationThunk
8688
context: ApiContext<EndpointDefinitions>
8789
assertTagType: AssertTagTypes
8890
config: Omit<
@@ -114,7 +116,7 @@ export function buildSlice({
114116
},
115117
extraReducers(builder) {
116118
builder
117-
.addCase(queryThunk.pending, (draft, { meta: { arg, requestId } }) => {
119+
.addCase(queryThunk.pending, (draft, { meta, meta: { arg } }) => {
118120
if (arg.subscribe) {
119121
// only initialize substate if we want to subscribe to it
120122
draft[arg.queryCacheKey] ??= {
@@ -125,9 +127,9 @@ export function buildSlice({
125127

126128
updateQuerySubstateIfExists(draft, arg.queryCacheKey, (substate) => {
127129
substate.status = QueryStatus.pending
128-
substate.requestId = requestId
130+
substate.requestId = meta.requestId
129131
substate.originalArgs = arg.originalArgs
130-
substate.startedTimeStamp = arg.startedTimeStamp
132+
substate.startedTimeStamp = meta.startedTimeStamp
131133
})
132134
})
133135
.addCase(queryThunk.fulfilled, (draft, { meta, payload }) => {
@@ -137,12 +139,9 @@ export function buildSlice({
137139
(substate) => {
138140
if (substate.requestId !== meta.requestId) return
139141
substate.status = QueryStatus.fulfilled
140-
substate.data = copyWithStructuralSharing(
141-
substate.data,
142-
payload.result
143-
)
142+
substate.data = copyWithStructuralSharing(substate.data, payload)
144143
delete substate.error
145-
substate.fulfilledTimeStamp = payload.fulfilledTimeStamp
144+
substate.fulfilledTimeStamp = meta.fulfilledTimeStamp
146145
}
147146
)
148147
})
@@ -184,26 +183,26 @@ export function buildSlice({
184183
builder
185184
.addCase(
186185
mutationThunk.pending,
187-
(draft, { meta: { arg, requestId } }) => {
186+
(draft, { meta: { arg, requestId, startedTimeStamp } }) => {
188187
if (!arg.track) return
189188

190189
draft[requestId] = {
191190
status: QueryStatus.pending,
192191
originalArgs: arg.originalArgs,
193192
endpointName: arg.endpointName,
194-
startedTimeStamp: arg.startedTimeStamp,
193+
startedTimeStamp,
195194
}
196195
}
197196
)
198197
.addCase(
199198
mutationThunk.fulfilled,
200-
(draft, { payload, meta: { requestId, arg } }) => {
201-
if (!arg.track) return
199+
(draft, { payload, meta, meta: { requestId } }) => {
200+
if (!meta.arg.track) return
202201

203202
updateMutationSubstateIfExists(draft, { requestId }, (substate) => {
204203
substate.status = QueryStatus.fulfilled
205-
substate.data = payload.result
206-
substate.fulfilledTimeStamp = payload.fulfilledTimeStamp
204+
substate.data = payload
205+
substate.fulfilledTimeStamp = meta.fulfilledTimeStamp
207206
})
208207
}
209208
)

0 commit comments

Comments
 (0)