From a77c60194d3772586a0ae02de53c36da2af56c75 Mon Sep 17 00:00:00 2001 From: Jesper Johansson <21033162+jesperjohansson@users.noreply.github.com> Date: Wed, 7 Aug 2024 14:52:40 +0200 Subject: [PATCH 1/8] Add "createAsyncThunkCreator" with option for customizing default error serializer --- docs/api/createAsyncThunk.mdx | 51 +++++++++++ packages/toolkit/src/createAsyncThunk.ts | 85 ++++++++++++++++--- packages/toolkit/src/index.ts | 1 + .../src/tests/createAsyncThunk.test.ts | 83 ++++++++++++++++++ 4 files changed, 208 insertions(+), 12 deletions(-) diff --git a/docs/api/createAsyncThunk.mdx b/docs/api/createAsyncThunk.mdx index 98d7fbb395..f9bcfc34f4 100644 --- a/docs/api/createAsyncThunk.mdx +++ b/docs/api/createAsyncThunk.mdx @@ -769,3 +769,54 @@ const UsersComponent = (props: { id: string }) => { // render UI here } ``` + +## `createAsyncThunkCreator` + +### Options + +An object with the following optional fields: + +- `serializeError(error: any, defaultSerializer: (error: any) => GetSerializedErrorType) => GetSerializedErrorType` to replace or extend the default internal serializer method with your own serialization logic. + +### Return Value + +`createAsyncThunkCreator` returns a Redux thunk action creator with customized options. Currently, the only option is `serializeError`. + +### Example + +```ts no-transpile +import { createAsyncThunkCreator } from '@reduxjs/toolkit' + +export interface AppSerializedError extends SerializedError { + isAxiosError?: boolean +} + +type ThunkApiConfig = { + state: RootState + serializedErrorType: AppSerializedError +} + +const createAppAsyncThunkCreator = createAsyncThunkCreator({ + serializeError(error, defaultSerializer) { + const serializedError = defaultSerializer(error) + serializedError.isAxiosError = error.isAxiosError + return serializedError + }, +}) + +function createAppAsyncThunk< + Returned, + ThunkArg = void, + T extends ThunkApiConfig = ThunkApiConfig, +>( + typePrefix: string, + payloadCreator: AsyncThunkPayloadCreator, + options?: AsyncThunkOptions, +): AsyncThunk { + return createAppAsyncThunkCreator( + typePrefix, + payloadCreator, + options, + ) +} +``` diff --git a/packages/toolkit/src/createAsyncThunk.ts b/packages/toolkit/src/createAsyncThunk.ts index 4534165869..aa1776b5d1 100644 --- a/packages/toolkit/src/createAsyncThunk.ts +++ b/packages/toolkit/src/createAsyncThunk.ts @@ -91,19 +91,21 @@ class FulfillWithMeta { * * @public */ -export const miniSerializeError = (value: any): SerializedError => { +export const miniSerializeError = ( + value: any, +): GetSerializedErrorType => { if (typeof value === 'object' && value !== null) { - const simpleError: SerializedError = {} + const simpleError = {} as Record for (const property of commonProperties) { if (typeof value[property] === 'string') { simpleError[property] = value[property] } } - return simpleError + return simpleError as GetSerializedErrorType } - return { message: String(value) } + return { message: String(value) } as GetSerializedErrorType } export type AsyncThunkConfig = { @@ -176,11 +178,14 @@ type GetRejectedMeta = ThunkApiConfig extends { ? RejectedMeta : unknown -type GetSerializedErrorType = ThunkApiConfig extends { +type GetSerializedErrorType< + ThunkApiConfig, + T extends SerializedError = SerializedError, +> = ThunkApiConfig extends { serializedErrorType: infer GetSerializedErrorType } ? GetSerializedErrorType - : SerializedError + : T type MaybePromise = T | Promise | (T extends any ? Promise : never) @@ -487,11 +492,21 @@ type CreateAsyncThunk = { > } -export const createAsyncThunk = /* @__PURE__ */ (() => { +type InternalCreateAsyncThunkCreatorOptions< + ThunkApiConfig extends AsyncThunkConfig, +> = { + serializeError?: ErrorSerializer +} + +function internalCreateAsyncThunkCreator< + CreatorThunkApiConfig extends AsyncThunkConfig = {}, +>( + creatorOptions?: InternalCreateAsyncThunkCreatorOptions, +): CreateAsyncThunk { function createAsyncThunk< Returned, ThunkArg, - ThunkApiConfig extends AsyncThunkConfig, + ThunkApiConfig extends CreatorThunkApiConfig, >( typePrefix: string, payloadCreator: AsyncThunkPayloadCreator< @@ -542,6 +557,18 @@ export const createAsyncThunk = /* @__PURE__ */ (() => { }), ) + function getError(x: unknown): GetSerializedErrorType { + if (options && options.serializeError) { + return options.serializeError(x) + } + + if (creatorOptions && creatorOptions.serializeError) { + return creatorOptions.serializeError(x, miniSerializeError) + } + + return miniSerializeError(x) + } + const rejected: AsyncThunkRejectedActionCreator = createAction( typePrefix + '/rejected', @@ -553,9 +580,7 @@ export const createAsyncThunk = /* @__PURE__ */ (() => { meta?: RejectedMeta, ) => ({ payload, - error: ((options && options.serializeError) || miniSerializeError)( - error || 'Rejected', - ) as GetSerializedErrorType, + error: getError(error || 'Rejected'), meta: { ...((meta as any) || {}), arg, @@ -588,7 +613,10 @@ export const createAsyncThunk = /* @__PURE__ */ (() => { const promise = (async function () { let finalAction: ReturnType try { - let conditionResult = options?.condition?.(arg, { getState, extra }) + let conditionResult = options?.condition?.(arg, { + getState, + extra, + }) if (isThenable(conditionResult)) { conditionResult = await conditionResult } @@ -702,9 +730,14 @@ export const createAsyncThunk = /* @__PURE__ */ (() => { }, ) } + createAsyncThunk.withTypes = () => createAsyncThunk return createAsyncThunk as CreateAsyncThunk +} + +export const createAsyncThunk = /* @__PURE__ */ (() => { + return internalCreateAsyncThunkCreator() as CreateAsyncThunk })() interface UnwrappableAction { @@ -744,3 +777,31 @@ function isThenable(value: any): value is PromiseLike { typeof value.then === 'function' ) } + +/** + * An error serializer function that can be used to serialize errors into plain objects. + * + * @param error - The error to serialize + * @param defaultSerializer - The original default serializer `miniSerializeError` https://redux-toolkit.js.org/api/other-exports/#miniserializeerror + * + * @public + */ +type ErrorSerializer = ( + error: any, + defaultSerializer: (error: any) => GetSerializedErrorType, +) => GetSerializedErrorType + +/** + * @public + */ +type CreateAsyncThunkCreatorOptions = { + serializeError?: ErrorSerializer +} + +export const createAsyncThunkCreator = /* @__PURE__ */ (() => { + return ( + options: CreateAsyncThunkCreatorOptions, + ): CreateAsyncThunk => { + return internalCreateAsyncThunkCreator(options) + } +})() diff --git a/packages/toolkit/src/index.ts b/packages/toolkit/src/index.ts index 1883310dc2..e784e21050 100644 --- a/packages/toolkit/src/index.ts +++ b/packages/toolkit/src/index.ts @@ -121,6 +121,7 @@ export type { export { createAsyncThunk, + createAsyncThunkCreator, unwrapResult, miniSerializeError, } from './createAsyncThunk' diff --git a/packages/toolkit/src/tests/createAsyncThunk.test.ts b/packages/toolkit/src/tests/createAsyncThunk.test.ts index ef6ae71838..7b4c21b451 100644 --- a/packages/toolkit/src/tests/createAsyncThunk.test.ts +++ b/packages/toolkit/src/tests/createAsyncThunk.test.ts @@ -5,6 +5,7 @@ import { createReducer, unwrapResult, miniSerializeError, + createAsyncThunkCreator, } from '@reduxjs/toolkit' import { vi } from 'vitest' @@ -991,3 +992,85 @@ describe('meta', () => { expect(thunk.fulfilled.type).toBe('a/fulfilled') }) }) +describe('createAsyncThunkCreator', () => { + test('custom default serializeError only', async () => { + function serializeError() { + return 'serialized!' + } + const errorObject = 'something else!' + + const store = configureStore({ + reducer: (state = [], action) => [...state, action], + }) + + const createAsyncThunk = createAsyncThunkCreator<{ + serializedErrorType: string + }>({ + serializeError, + }) + + const asyncThunk = createAsyncThunk< + unknown, + void, + { serializedErrorType: string } + >('test', () => Promise.reject(errorObject), { serializeError }) + const rejected = await store.dispatch(asyncThunk()) + if (!asyncThunk.rejected.match(rejected)) { + throw new Error() + } + + const expectation = { + type: 'test/rejected', + payload: undefined, + error: 'serialized!', + meta: expect.any(Object), + } + expect(rejected).toEqual(expectation) + expect(store.getState()[2]).toEqual(expectation) + expect(rejected.error).not.toEqual(miniSerializeError(errorObject)) + }) + + test('custom default serializeError with thunk-level override', async () => { + function defaultSerializeError() { + return 'serialized by default serializer!' + } + function thunkSerializeError() { + return 'serialized by thunk serializer!' + } + const errorObject = 'something else!' + + const store = configureStore({ + reducer: (state = [], action) => [...state, action], + }) + + const createAsyncThunk = createAsyncThunkCreator<{ + serializedErrorType: string + }>({ + serializeError: defaultSerializeError, + }) + + const thunk = createAsyncThunk< + unknown, + void, + { serializedErrorType: string } + >('test', () => Promise.reject(errorObject), { + serializeError: thunkSerializeError, + }) + const rejected = await store.dispatch(thunk()) + if (!thunk.rejected.match(rejected)) { + throw new Error() + } + + const thunkLevelExpectation = { + type: 'test/rejected', + payload: undefined, + error: 'serialized by thunk serializer!', + meta: expect.any(Object), + } + + expect(rejected).toEqual(thunkLevelExpectation) + expect(store.getState()[2]).toEqual(thunkLevelExpectation) + expect(rejected.error).not.toEqual(miniSerializeError(errorObject)) + expect(rejected.error).not.toEqual('serialized by default serializer!') + }) +}) From a679dc344104fe3c0134acc880437cc3c563f92e Mon Sep 17 00:00:00 2001 From: Jesper Johansson <21033162+jesperjohansson@users.noreply.github.com> Date: Wed, 7 Aug 2024 14:57:11 +0200 Subject: [PATCH 2/8] Update example for createAsyncThunkCreator --- docs/api/createAsyncThunk.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/createAsyncThunk.mdx b/docs/api/createAsyncThunk.mdx index f9bcfc34f4..134b3ae97a 100644 --- a/docs/api/createAsyncThunk.mdx +++ b/docs/api/createAsyncThunk.mdx @@ -785,7 +785,7 @@ An object with the following optional fields: ### Example ```ts no-transpile -import { createAsyncThunkCreator } from '@reduxjs/toolkit' +import { createAsyncThunkCreator, SerializedError } from '@reduxjs/toolkit' export interface AppSerializedError extends SerializedError { isAxiosError?: boolean From 871b6712b24534f5a995267e878a9317db29e8ef Mon Sep 17 00:00:00 2001 From: Jesper Johansson <21033162+jesperjohansson@users.noreply.github.com> Date: Wed, 7 Aug 2024 17:45:57 +0200 Subject: [PATCH 3/8] Revert miniSerializeError type changes --- docs/api/createAsyncThunk.mdx | 4 ++-- packages/toolkit/src/createAsyncThunk.ts | 19 +++++++------------ 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/docs/api/createAsyncThunk.mdx b/docs/api/createAsyncThunk.mdx index 134b3ae97a..2496d5567b 100644 --- a/docs/api/createAsyncThunk.mdx +++ b/docs/api/createAsyncThunk.mdx @@ -776,7 +776,7 @@ const UsersComponent = (props: { id: string }) => { An object with the following optional fields: -- `serializeError(error: any, defaultSerializer: (error: any) => GetSerializedErrorType) => GetSerializedErrorType` to replace or extend the default internal serializer method with your own serialization logic. +- `serializeError(error: any, defaultSerializer: (error: any) => SerializedError) => GetSerializedErrorType` to replace or extend the default internal serializer method with your own serialization logic. ### Return Value @@ -798,7 +798,7 @@ type ThunkApiConfig = { const createAppAsyncThunkCreator = createAsyncThunkCreator({ serializeError(error, defaultSerializer) { - const serializedError = defaultSerializer(error) + const serializedError = defaultSerializer(error) as AppSerializedError serializedError.isAxiosError = error.isAxiosError return serializedError }, diff --git a/packages/toolkit/src/createAsyncThunk.ts b/packages/toolkit/src/createAsyncThunk.ts index aa1776b5d1..2dee13ee01 100644 --- a/packages/toolkit/src/createAsyncThunk.ts +++ b/packages/toolkit/src/createAsyncThunk.ts @@ -91,9 +91,7 @@ class FulfillWithMeta { * * @public */ -export const miniSerializeError = ( - value: any, -): GetSerializedErrorType => { +export const miniSerializeError = (value: any): SerializedError => { if (typeof value === 'object' && value !== null) { const simpleError = {} as Record for (const property of commonProperties) { @@ -102,10 +100,10 @@ export const miniSerializeError = ( } } - return simpleError as GetSerializedErrorType + return simpleError } - return { message: String(value) } as GetSerializedErrorType + return { message: String(value) } } export type AsyncThunkConfig = { @@ -178,14 +176,11 @@ type GetRejectedMeta = ThunkApiConfig extends { ? RejectedMeta : unknown -type GetSerializedErrorType< - ThunkApiConfig, - T extends SerializedError = SerializedError, -> = ThunkApiConfig extends { +type GetSerializedErrorType = ThunkApiConfig extends { serializedErrorType: infer GetSerializedErrorType } ? GetSerializedErrorType - : T + : SerializedError type MaybePromise = T | Promise | (T extends any ? Promise : never) @@ -566,7 +561,7 @@ function internalCreateAsyncThunkCreator< return creatorOptions.serializeError(x, miniSerializeError) } - return miniSerializeError(x) + return miniSerializeError(x) as GetSerializedErrorType } const rejected: AsyncThunkRejectedActionCreator = @@ -788,7 +783,7 @@ function isThenable(value: any): value is PromiseLike { */ type ErrorSerializer = ( error: any, - defaultSerializer: (error: any) => GetSerializedErrorType, + defaultSerializer: (error: any) => SerializedError, ) => GetSerializedErrorType /** From adc78ed95c0893af9fd799a71952f06be90297ca Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Fri, 13 Sep 2024 12:14:32 +0100 Subject: [PATCH 4/8] tweak createAsyncThunkCreator and allow overriding idGenerator --- packages/toolkit/src/createAsyncThunk.ts | 112 ++++++++---------- .../src/tests/createAsyncThunk.test.ts | 47 +++++++- 2 files changed, 91 insertions(+), 68 deletions(-) diff --git a/packages/toolkit/src/createAsyncThunk.ts b/packages/toolkit/src/createAsyncThunk.ts index 2dee13ee01..8154d630b8 100644 --- a/packages/toolkit/src/createAsyncThunk.ts +++ b/packages/toolkit/src/createAsyncThunk.ts @@ -487,35 +487,62 @@ type CreateAsyncThunk = { > } -type InternalCreateAsyncThunkCreatorOptions< +/** + * @public + */ +export type CreateAsyncThunkCreatorOptions< ThunkApiConfig extends AsyncThunkConfig, -> = { - serializeError?: ErrorSerializer -} +> = Pick< + AsyncThunkOptions, + 'serializeError' | 'idGenerator' +> -function internalCreateAsyncThunkCreator< +export function createAsyncThunkCreator< CreatorThunkApiConfig extends AsyncThunkConfig = {}, >( - creatorOptions?: InternalCreateAsyncThunkCreatorOptions, + creatorOptions?: CreateAsyncThunkCreatorOptions, ): CreateAsyncThunk { function createAsyncThunk< Returned, ThunkArg, - ThunkApiConfig extends CreatorThunkApiConfig, + CallThunkApiConfig extends AsyncThunkConfig, >( typePrefix: string, payloadCreator: AsyncThunkPayloadCreator< Returned, ThunkArg, - ThunkApiConfig + OverrideThunkApiConfigs + >, + options?: AsyncThunkOptions< + ThunkArg, + OverrideThunkApiConfigs >, - options?: AsyncThunkOptions, - ): AsyncThunk { + ): AsyncThunk< + Returned, + ThunkArg, + OverrideThunkApiConfigs + > { + type ThunkApiConfig = OverrideThunkApiConfigs< + CreatorThunkApiConfig, + CallThunkApiConfig + > type RejectedValue = GetRejectValue type PendingMeta = GetPendingMeta type FulfilledMeta = GetFulfilledMeta type RejectedMeta = GetRejectedMeta + const { + serializeError = miniSerializeError, + // nanoid needs to be wrapped because it accepts a size argument + idGenerator = () => nanoid(), + getPendingMeta, + condition, + dispatchConditionRejection, + } = { + ...creatorOptions, + ...options, + } + const fulfilled: AsyncThunkFulfilledActionCreator< Returned, ThunkArg, @@ -552,18 +579,6 @@ function internalCreateAsyncThunkCreator< }), ) - function getError(x: unknown): GetSerializedErrorType { - if (options && options.serializeError) { - return options.serializeError(x) - } - - if (creatorOptions && creatorOptions.serializeError) { - return creatorOptions.serializeError(x, miniSerializeError) - } - - return miniSerializeError(x) as GetSerializedErrorType - } - const rejected: AsyncThunkRejectedActionCreator = createAction( typePrefix + '/rejected', @@ -575,7 +590,9 @@ function internalCreateAsyncThunkCreator< meta?: RejectedMeta, ) => ({ payload, - error: getError(error || 'Rejected'), + error: serializeError( + error || 'Rejected', + ) as GetSerializedErrorType, meta: { ...((meta as any) || {}), arg, @@ -592,9 +609,7 @@ function internalCreateAsyncThunkCreator< arg: ThunkArg, ): AsyncThunkAction> { return (dispatch, getState, extra) => { - const requestId = options?.idGenerator - ? options.idGenerator(arg) - : nanoid() + const requestId = idGenerator(arg) const abortController = new AbortController() let abortHandler: (() => void) | undefined @@ -608,7 +623,7 @@ function internalCreateAsyncThunkCreator< const promise = (async function () { let finalAction: ReturnType try { - let conditionResult = options?.condition?.(arg, { + let conditionResult = condition?.(arg, { getState, extra, }) @@ -637,10 +652,7 @@ function internalCreateAsyncThunkCreator< pending( requestId, arg, - options?.getPendingMeta?.( - { requestId, arg }, - { getState, extra }, - ), + getPendingMeta?.({ requestId, arg }, { getState, extra }), ) as any, ) finalAction = await Promise.race([ @@ -689,8 +701,7 @@ function internalCreateAsyncThunkCreator< // and https://github.com/reduxjs/redux-toolkit/blob/e85eb17b39a2118d859f7b7746e0f3fee523e089/docs/tutorials/advanced-tutorial.md#async-error-handling-logic-in-thunks const skipDispatch = - options && - !options.dispatchConditionRejection && + !dispatchConditionRejection && rejected.match(finalAction) && (finalAction as any).meta.condition @@ -728,12 +739,11 @@ function internalCreateAsyncThunkCreator< createAsyncThunk.withTypes = () => createAsyncThunk - return createAsyncThunk as CreateAsyncThunk + return createAsyncThunk as CreateAsyncThunk } -export const createAsyncThunk = /* @__PURE__ */ (() => { - return internalCreateAsyncThunkCreator() as CreateAsyncThunk -})() +export const createAsyncThunk = + /* @__PURE__ */ createAsyncThunkCreator() interface UnwrappableAction { payload: any @@ -772,31 +782,3 @@ function isThenable(value: any): value is PromiseLike { typeof value.then === 'function' ) } - -/** - * An error serializer function that can be used to serialize errors into plain objects. - * - * @param error - The error to serialize - * @param defaultSerializer - The original default serializer `miniSerializeError` https://redux-toolkit.js.org/api/other-exports/#miniserializeerror - * - * @public - */ -type ErrorSerializer = ( - error: any, - defaultSerializer: (error: any) => SerializedError, -) => GetSerializedErrorType - -/** - * @public - */ -type CreateAsyncThunkCreatorOptions = { - serializeError?: ErrorSerializer -} - -export const createAsyncThunkCreator = /* @__PURE__ */ (() => { - return ( - options: CreateAsyncThunkCreatorOptions, - ): CreateAsyncThunk => { - return internalCreateAsyncThunkCreator(options) - } -})() diff --git a/packages/toolkit/src/tests/createAsyncThunk.test.ts b/packages/toolkit/src/tests/createAsyncThunk.test.ts index 7b4c21b451..cfcc2f2cc7 100644 --- a/packages/toolkit/src/tests/createAsyncThunk.test.ts +++ b/packages/toolkit/src/tests/createAsyncThunk.test.ts @@ -1035,7 +1035,7 @@ describe('createAsyncThunkCreator', () => { return 'serialized by default serializer!' } function thunkSerializeError() { - return 'serialized by thunk serializer!' + return { message: 'serialized by thunk serializer!' } } const errorObject = 'something else!' @@ -1052,7 +1052,11 @@ describe('createAsyncThunkCreator', () => { const thunk = createAsyncThunk< unknown, void, - { serializedErrorType: string } + { + serializedErrorType: { + message: string + } + } >('test', () => Promise.reject(errorObject), { serializeError: thunkSerializeError, }) @@ -1064,7 +1068,7 @@ describe('createAsyncThunkCreator', () => { const thunkLevelExpectation = { type: 'test/rejected', payload: undefined, - error: 'serialized by thunk serializer!', + error: { message: 'serialized by thunk serializer!' }, meta: expect.any(Object), } @@ -1073,4 +1077,41 @@ describe('createAsyncThunkCreator', () => { expect(rejected.error).not.toEqual(miniSerializeError(errorObject)) expect(rejected.error).not.toEqual('serialized by default serializer!') }) + test('custom default idGenerator only', async () => { + function idGenerator(arg: unknown) { + return `${arg}` + } + const createAsyncThunk = createAsyncThunkCreator({ + idGenerator, + }) + const asyncThunk = createAsyncThunk('test', async () => 1) + const store = configureStore({ + reducer: (state = [], action) => [...state, action], + }) + const promise = store.dispatch(asyncThunk('testArg')) + expect(promise.requestId).toBe('testArg') + const result = await promise + expect(result.meta.requestId).toBe('testArg') + }) + test('custom default idGenerator with thunk-level override', async () => { + function defaultIdGenerator(arg: unknown) { + return `default-${arg}` + } + function thunkIdGenerator(arg: unknown) { + return `thunk-${arg}` + } + const createAsyncThunk = createAsyncThunkCreator({ + idGenerator: defaultIdGenerator, + }) + const thunk = createAsyncThunk('test', async () => 1, { + idGenerator: thunkIdGenerator, + }) + const store = configureStore({ + reducer: (state = [], action) => [...state, action], + }) + const promise = store.dispatch(thunk('testArg')) + expect(promise.requestId).toBe('thunk-testArg') + const result = await promise + expect(result.meta.requestId).toBe('thunk-testArg') + }) }) From bf389f7f296df164009433a01a65b357c0a12180 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Fri, 13 Sep 2024 12:15:50 +0100 Subject: [PATCH 5/8] change simpleError type back --- packages/toolkit/src/createAsyncThunk.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/src/createAsyncThunk.ts b/packages/toolkit/src/createAsyncThunk.ts index 8154d630b8..002eebb068 100644 --- a/packages/toolkit/src/createAsyncThunk.ts +++ b/packages/toolkit/src/createAsyncThunk.ts @@ -93,7 +93,7 @@ class FulfillWithMeta { */ export const miniSerializeError = (value: any): SerializedError => { if (typeof value === 'object' && value !== null) { - const simpleError = {} as Record + const simpleError: SerializedError = {} for (const property of commonProperties) { if (typeof value[property] === 'string') { simpleError[property] = value[property] From 301319da9f5ee57fa9ad2cc4728bec549d7f6b9d Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Fri, 13 Sep 2024 12:22:00 +0100 Subject: [PATCH 6/8] fix type error --- packages/toolkit/src/createAsyncThunk.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/src/createAsyncThunk.ts b/packages/toolkit/src/createAsyncThunk.ts index 002eebb068..aba27a2f36 100644 --- a/packages/toolkit/src/createAsyncThunk.ts +++ b/packages/toolkit/src/createAsyncThunk.ts @@ -541,7 +541,7 @@ export function createAsyncThunkCreator< } = { ...creatorOptions, ...options, - } + } as AsyncThunkOptions const fulfilled: AsyncThunkFulfilledActionCreator< Returned, From 13767f8faf1f1ac785cc015c5e9511b329fa0161 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Fri, 13 Sep 2024 12:38:44 +0100 Subject: [PATCH 7/8] update docs and add type test for config --- docs/api/createAsyncThunk.mdx | 45 +++++++++---------- .../src/tests/createAsyncThunk.test-d.ts | 25 +++++++++++ 2 files changed, 46 insertions(+), 24 deletions(-) diff --git a/docs/api/createAsyncThunk.mdx b/docs/api/createAsyncThunk.mdx index 2496d5567b..267675725f 100644 --- a/docs/api/createAsyncThunk.mdx +++ b/docs/api/createAsyncThunk.mdx @@ -772,20 +772,31 @@ const UsersComponent = (props: { id: string }) => { ## `createAsyncThunkCreator` +Create a customised version of `createAsyncThunk` with defaulted options. + +Options specified when calling `createAsyncThunk` will override options specified in `createAsyncThunkCreator`. + ### Options An object with the following optional fields: -- `serializeError(error: any, defaultSerializer: (error: any) => SerializedError) => GetSerializedErrorType` to replace or extend the default internal serializer method with your own serialization logic. +- `serializeError(error: unknown) => GetSerializedErrorType` to replace or extend the default serializer method with your own serialization logic. +- `idGenerator(arg: unknown) => string`: a function to use when generating the `requestId` for the request sequence. Defaults to use [nanoid](./otherExports.mdx/#nanoid), but you can implement your own ID generation logic. ### Return Value -`createAsyncThunkCreator` returns a Redux thunk action creator with customized options. Currently, the only option is `serializeError`. +A version of `createAsyncThunk` that has options defaulted to the values provided. ### Example ```ts no-transpile -import { createAsyncThunkCreator, SerializedError } from '@reduxjs/toolkit' +import { + createAsyncThunkCreator, + miniSerializeError, + SerializedError, +} from '@reduxjs/toolkit' +import { isAxiosError } from 'axios' +import { v4 as uuidv4 } from 'uuid' export interface AppSerializedError extends SerializedError { isAxiosError?: boolean @@ -796,27 +807,13 @@ type ThunkApiConfig = { serializedErrorType: AppSerializedError } -const createAppAsyncThunkCreator = createAsyncThunkCreator({ - serializeError(error, defaultSerializer) { - const serializedError = defaultSerializer(error) as AppSerializedError - serializedError.isAxiosError = error.isAxiosError - return serializedError +export const createAppAsyncThunk = createAsyncThunkCreator({ + serializeError(error) { + return { + ...miniSerializeError(error), + isAxiosError: isAxiosError(error), + } }, + idGenerator: () => uuidv4(), }) - -function createAppAsyncThunk< - Returned, - ThunkArg = void, - T extends ThunkApiConfig = ThunkApiConfig, ->( - typePrefix: string, - payloadCreator: AsyncThunkPayloadCreator, - options?: AsyncThunkOptions, -): AsyncThunk { - return createAppAsyncThunkCreator( - typePrefix, - payloadCreator, - options, - ) -} ``` diff --git a/packages/toolkit/src/tests/createAsyncThunk.test-d.ts b/packages/toolkit/src/tests/createAsyncThunk.test-d.ts index 7f961666e9..9d6dafdb40 100644 --- a/packages/toolkit/src/tests/createAsyncThunk.test-d.ts +++ b/packages/toolkit/src/tests/createAsyncThunk.test-d.ts @@ -1,4 +1,5 @@ import type { + Action, AsyncThunk, SerializedError, ThunkDispatch, @@ -7,6 +8,7 @@ import type { import { configureStore, createAsyncThunk, + createAsyncThunkCreator, createReducer, createSlice, unwrapResult, @@ -888,4 +890,27 @@ describe('type tests', () => { expectTypeOf(ret.meta).not.toHaveProperty('extraProp') } }) + test('createAsyncThunkCreator', () => { + const store = configureStore({ + reducer: (state: Action[] = [], action) => [...state, action], + }) + + type RootState = ReturnType + type AppDispatch = typeof store.dispatch + + const createAsyncThunk = createAsyncThunkCreator<{ + state: RootState + dispatch: AppDispatch + }>() + + const thunk = createAsyncThunk( + 'test', + (arg: string, { dispatch, getState }) => { + expectTypeOf(dispatch).toEqualTypeOf() + expectTypeOf(getState).toEqualTypeOf<() => RootState>() + }, + ) + + store.dispatch(thunk('test')) + }) }) From 66f7494816f8e5f50fbcf3359b2a30ca7f2da3dd Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Fri, 13 Sep 2024 14:03:11 +0100 Subject: [PATCH 8/8] make serializeError description consistent --- docs/api/createAsyncThunk.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/createAsyncThunk.mdx b/docs/api/createAsyncThunk.mdx index 267675725f..dc4ceae2db 100644 --- a/docs/api/createAsyncThunk.mdx +++ b/docs/api/createAsyncThunk.mdx @@ -780,7 +780,7 @@ Options specified when calling `createAsyncThunk` will override options specifie An object with the following optional fields: -- `serializeError(error: unknown) => GetSerializedErrorType` to replace or extend the default serializer method with your own serialization logic. +- `serializeError(error: unknown) => any` to replace the internal `miniSerializeError` method with your own serialization logic. - `idGenerator(arg: unknown) => string`: a function to use when generating the `requestId` for the request sequence. Defaults to use [nanoid](./otherExports.mdx/#nanoid), but you can implement your own ID generation logic. ### Return Value