Skip to content

Commit

Permalink
Merge pull request #1084 from reduxjs/feature/thunk-shape
Browse files Browse the repository at this point in the history
  • Loading branch information
markerikson authored Jun 3, 2021
2 parents 80daf2f + dcd4c8d commit ada1f4b
Show file tree
Hide file tree
Showing 20 changed files with 305 additions and 123 deletions.
4 changes: 3 additions & 1 deletion src/createAsyncThunk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,9 @@ export type AsyncThunkRejectedActionCreator<
aborted: boolean
condition: boolean
} & (
| { rejectedWithValue: false }
| ({ rejectedWithValue: false } & {
[K in keyof GetRejectedMeta<ThunkApiConfig>]?: undefined
})
| ({ rejectedWithValue: true } & GetRejectedMeta<ThunkApiConfig>)
)
>
Expand Down
2 changes: 1 addition & 1 deletion src/query/baseQueryTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export type BaseQueryMeta<BaseQuery extends BaseQueryFn> = UnwrapPromise<

export type BaseQueryError<BaseQuery extends BaseQueryFn> = Exclude<
UnwrapPromise<ReturnType<BaseQuery>>,
{ error: undefined }
{ error?: undefined }
>['error']

export type BaseQueryArg<
Expand Down
29 changes: 11 additions & 18 deletions src/query/core/buildInitiate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import type {
QueryArgFrom,
ResultTypeFrom,
} from '../endpointDefinitions'
import type { QueryThunkArg, MutationThunkArg } from './buildThunks'
import type {
QueryThunkArg,
MutationThunkArg,
QueryThunk,
MutationThunk,
} from './buildThunks'
import type {
AnyAction,
AsyncThunk,
Expand Down Expand Up @@ -108,10 +113,6 @@ export type MutationActionCreatorResult<
* Whether the mutation is being tracked in the store.
*/
track?: boolean
/**
* Timestamp for when the mutation was initiated
*/
startedTimeStamp: number
}
/**
* A unique string generated for the request sequence
Expand Down Expand Up @@ -186,8 +187,8 @@ export function buildInitiate({
api,
}: {
serializeQueryArgs: InternalSerializeQueryArgs
queryThunk: AsyncThunk<any, QueryThunkArg, {}>
mutationThunk: AsyncThunk<any, MutationThunkArg, {}>
queryThunk: QueryThunk
mutationThunk: MutationThunk
api: Api<any, EndpointDefinitions, any, any>
}) {
const {
Expand Down Expand Up @@ -234,7 +235,6 @@ Features like automatic cache collection, automatic refetching etc. will not be
endpointName,
originalArgs: arg,
queryCacheKey,
startedTimeStamp: Date.now(),
})
const thunkResult = dispatch(thunk)
middlewareWarning(getState)
Expand Down Expand Up @@ -289,26 +289,19 @@ Features like automatic cache collection, automatic refetching etc. will not be
endpointName,
originalArgs: arg,
track,
startedTimeStamp: Date.now(),
})
const thunkResult = dispatch(thunk)
middlewareWarning(getState)
const { requestId, abort } = thunkResult
const returnValuePromise = thunkResult
.then(unwrapResult)
.then((unwrapped) => ({
data: unwrapped.result,
}))
.unwrap()
.then((data) => ({ data }))
.catch((error) => ({ error }))
return Object.assign(returnValuePromise, {
arg: thunkResult.arg,
requestId,
abort,
unwrap() {
return thunkResult
.then(unwrapResult)
.then((unwrapped) => unwrapped.result)
},
unwrap: thunkResult.unwrap,
unsubscribe() {
if (track) dispatch(unsubscribeMutationResult({ requestId }))
},
Expand Down
26 changes: 18 additions & 8 deletions src/query/core/buildMiddleware/cacheLifecycle.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { isAsyncThunkAction, isFulfilled } from '@reduxjs/toolkit'
import type { AnyAction } from 'redux'
import type { ThunkDispatch } from 'redux-thunk'
import type { BaseQueryFn } from '../../baseQueryTypes'
import type { BaseQueryFn, BaseQueryMeta } from '../../baseQueryTypes'
import { DefinitionType } from '../../endpointDefinitions'
import type { RootState } from '../apiState'
import type {
Expand Down Expand Up @@ -78,7 +78,10 @@ declare module '../../endpointDefinitions' {
requestId: string
}

export interface CacheLifecyclePromises<ResultType = unknown> {
export interface CacheLifecyclePromises<
ResultType = unknown,
MetaType = unknown
> {
/**
* Promise that will resolve with the first value for this cache key.
* This allows you to `await` until an actual value is in cache.
Expand All @@ -98,6 +101,10 @@ declare module '../../endpointDefinitions' {
* The (transformed) query result.
*/
data: ResultType
/**
* The `meta` returned by the `baseQuery`
*/
meta: MetaType
},
typeof neverResolvedError
>
Expand All @@ -115,7 +122,7 @@ declare module '../../endpointDefinitions' {
ResultType,
ReducerPath extends string = string
> extends QueryBaseLifecycleApi<QueryArg, BaseQuery, ResultType, ReducerPath>,
CacheLifecyclePromises<ResultType> {}
CacheLifecyclePromises<ResultType, BaseQueryMeta<BaseQuery>> {}

export interface MutationCacheLifecycleApi<
QueryArg,
Expand All @@ -128,7 +135,7 @@ declare module '../../endpointDefinitions' {
ResultType,
ReducerPath
>,
CacheLifecyclePromises<ResultType> {}
CacheLifecyclePromises<ResultType, BaseQueryMeta<BaseQuery>> {}

interface QueryExtraOptions<
TagTypes extends string,
Expand Down Expand Up @@ -181,7 +188,7 @@ export const build: SubMiddlewareBuilder = ({

return (mwApi) => {
type CacheLifecycle = {
valueResolved?(value: { data: unknown }): unknown
valueResolved?(value: { data: unknown; meta: unknown }): unknown
cacheEntryRemoved(): void
}
const lifecycleMap: Record<string, CacheLifecycle> = {}
Expand Down Expand Up @@ -219,7 +226,10 @@ export const build: SubMiddlewareBuilder = ({
} else if (isFullfilledThunk(action)) {
const lifecycle = lifecycleMap[cacheKey]
if (lifecycle?.valueResolved) {
lifecycle.valueResolved({ data: action.payload.result })
lifecycle.valueResolved({
data: action.payload,
meta: action.meta.baseQueryMeta,
})
delete lifecycle.valueResolved
}
} else if (
Expand Down Expand Up @@ -268,10 +278,10 @@ export const build: SubMiddlewareBuilder = ({
lifecycle.cacheEntryRemoved = resolve
})
const cacheDataLoaded: PromiseWithKnownReason<
{ data: unknown },
{ data: unknown; meta: unknown },
typeof neverResolvedError
> = Promise.race([
new Promise<{ data: unknown }>((resolve) => {
new Promise<{ data: unknown; meta: unknown }>((resolve) => {
lifecycle.valueResolved = resolve
}),
cacheEntryRemoved.then(() => {
Expand Down
1 change: 0 additions & 1 deletion src/query/core/buildMiddleware/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ export function buildMiddleware<
originalArgs: querySubState.originalArgs,
subscribe: false,
forceRefetch: true,
startedTimeStamp: Date.now(),
queryCacheKey: queryCacheKey as any,
...override,
})
Expand Down
31 changes: 24 additions & 7 deletions src/query/core/buildMiddleware/queryLifecycle.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { isPending, isRejected, isFulfilled } from '@reduxjs/toolkit'
import type { BaseQueryError, BaseQueryFn } from '../../baseQueryTypes'
import type {
BaseQueryError,
BaseQueryFn,
BaseQueryMeta,
} from '../../baseQueryTypes'
import { DefinitionType } from '../../endpointDefinitions'
import type { QueryFulfilledRejectionReason } from '../../endpointDefinitions'
import type { Recipe } from '../buildThunks'
Expand Down Expand Up @@ -31,6 +35,10 @@ declare module '../../endpointDefinitions' {
* The (transformed) query result.
*/
data: ResultType
/**
* The `meta` returned by the `baseQuery`
*/
meta: BaseQueryMeta<BaseQuery>
},
QueryFulfilledRejectionReason<BaseQuery>
>
Expand All @@ -43,9 +51,14 @@ declare module '../../endpointDefinitions' {
* If this is `false`, that means this error was returned from the `baseQuery` or `queryFn` in a controlled manner.
*/
isUnhandledError: false
/**
* The `meta` returned by the `baseQuery`
*/
meta: BaseQueryMeta<BaseQuery>
}
| {
error: unknown
meta?: undefined
/**
* 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.
* There can not be made any assumption about the shape of `error`.
Expand Down Expand Up @@ -113,8 +126,8 @@ export const build: SubMiddlewareBuilder = ({

return (mwApi) => {
type CacheLifecycle = {
resolve(value: { data: unknown }): unknown
reject(value: { error: unknown; isUnhandledError: boolean }): unknown
resolve(value: { data: unknown; meta: unknown }): unknown
reject(value: QueryFulfilledRejectionReason<any>): unknown
}
const lifecycleMap: Record<string, CacheLifecycle> = {}

Expand All @@ -131,7 +144,7 @@ export const build: SubMiddlewareBuilder = ({
if (onQueryStarted) {
const lifecycle = {} as CacheLifecycle
const queryFulfilled = new (Promise as PromiseConstructorWithKnownReason)<
{ data: unknown },
{ data: unknown; meta: unknown },
QueryFulfilledRejectionReason<any>
>((resolve, reject) => {
lifecycle.resolve = resolve
Expand Down Expand Up @@ -168,14 +181,18 @@ export const build: SubMiddlewareBuilder = ({
onQueryStarted(originalArgs, lifecycleApi)
}
} else if (isFullfilledThunk(action)) {
const { requestId } = action.meta
lifecycleMap[requestId]?.resolve({ data: action.payload.result })
const { requestId, baseQueryMeta } = action.meta
lifecycleMap[requestId]?.resolve({
data: action.payload,
meta: baseQueryMeta,
})
delete lifecycleMap[requestId]
} else if (isRejectedThunk(action)) {
const { requestId, rejectedWithValue } = action.meta
const { requestId, rejectedWithValue, baseQueryMeta } = action.meta
lifecycleMap[requestId]?.reject({
error: action.payload ?? action.error,
isUnhandledError: !rejectedWithValue,
meta: baseQueryMeta as any,
})
delete lifecycleMap[requestId]
}
Expand Down
16 changes: 12 additions & 4 deletions src/query/core/buildMiddleware/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,17 @@ import type {
} from '@reduxjs/toolkit'

import type { Api, ApiContext } from '../../apiTypes'
import type { AssertTagTypes, EndpointDefinitions } from '../../endpointDefinitions'
import type {
AssertTagTypes,
EndpointDefinitions,
} from '../../endpointDefinitions'
import type { QueryStatus, QuerySubState, RootState } from '../apiState'
import type { MutationThunkArg, QueryThunkArg, ThunkResult } from '../buildThunks'
import type {
MutationThunk,
QueryThunk,
QueryThunkArg,
ThunkResult,
} from '../buildThunks'

export type QueryStateMeta<T> = Record<string, undefined | T>
export type TimeoutId = ReturnType<typeof setTimeout>
Expand All @@ -22,8 +30,8 @@ export interface BuildMiddlewareInput<
> {
reducerPath: ReducerPath
context: ApiContext<Definitions>
queryThunk: AsyncThunk<ThunkResult, QueryThunkArg, {}>
mutationThunk: AsyncThunk<ThunkResult, MutationThunkArg, {}>
queryThunk: QueryThunk
mutationThunk: MutationThunk
api: Api<any, Definitions, ReducerPath, TagTypes>
assertTagType: AssertTagTypes
}
Expand Down
31 changes: 15 additions & 16 deletions src/query/core/buildSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ import type {
} from './apiState'
import { QueryStatus } from './apiState'
import type {
MutationThunk,
MutationThunkArg,
QueryThunk,
QueryThunkArg,
ThunkResult,
} from './buildThunks'
Expand Down Expand Up @@ -81,8 +83,8 @@ export function buildSlice({
config,
}: {
reducerPath: string
queryThunk: AsyncThunk<ThunkResult, QueryThunkArg, {}>
mutationThunk: AsyncThunk<ThunkResult, MutationThunkArg, {}>
queryThunk: QueryThunk
mutationThunk: MutationThunk
context: ApiContext<EndpointDefinitions>
assertTagType: AssertTagTypes
config: Omit<
Expand Down Expand Up @@ -114,7 +116,7 @@ export function buildSlice({
},
extraReducers(builder) {
builder
.addCase(queryThunk.pending, (draft, { meta: { arg, requestId } }) => {
.addCase(queryThunk.pending, (draft, { meta, meta: { arg } }) => {
if (arg.subscribe) {
// only initialize substate if we want to subscribe to it
draft[arg.queryCacheKey] ??= {
Expand All @@ -125,9 +127,9 @@ export function buildSlice({

updateQuerySubstateIfExists(draft, arg.queryCacheKey, (substate) => {
substate.status = QueryStatus.pending
substate.requestId = requestId
substate.requestId = meta.requestId
substate.originalArgs = arg.originalArgs
substate.startedTimeStamp = arg.startedTimeStamp
substate.startedTimeStamp = meta.startedTimeStamp
})
})
.addCase(queryThunk.fulfilled, (draft, { meta, payload }) => {
Expand All @@ -137,12 +139,9 @@ export function buildSlice({
(substate) => {
if (substate.requestId !== meta.requestId) return
substate.status = QueryStatus.fulfilled
substate.data = copyWithStructuralSharing(
substate.data,
payload.result
)
substate.data = copyWithStructuralSharing(substate.data, payload)
delete substate.error
substate.fulfilledTimeStamp = payload.fulfilledTimeStamp
substate.fulfilledTimeStamp = meta.fulfilledTimeStamp
}
)
})
Expand Down Expand Up @@ -184,26 +183,26 @@ export function buildSlice({
builder
.addCase(
mutationThunk.pending,
(draft, { meta: { arg, requestId } }) => {
(draft, { meta: { arg, requestId, startedTimeStamp } }) => {
if (!arg.track) return

draft[requestId] = {
status: QueryStatus.pending,
originalArgs: arg.originalArgs,
endpointName: arg.endpointName,
startedTimeStamp: arg.startedTimeStamp,
startedTimeStamp,
}
}
)
.addCase(
mutationThunk.fulfilled,
(draft, { payload, meta: { requestId, arg } }) => {
if (!arg.track) return
(draft, { payload, meta, meta: { requestId } }) => {
if (!meta.arg.track) return

updateMutationSubstateIfExists(draft, { requestId }, (substate) => {
substate.status = QueryStatus.fulfilled
substate.data = payload.result
substate.fulfilledTimeStamp = payload.fulfilledTimeStamp
substate.data = payload
substate.fulfilledTimeStamp = meta.fulfilledTimeStamp
})
}
)
Expand Down
Loading

0 comments on commit ada1f4b

Please sign in to comment.