Skip to content

Commit

Permalink
Add idGenerator option to createAsyncThunk (#743) (#976)
Browse files Browse the repository at this point in the history
  • Loading branch information
Shrugsy committed Apr 2, 2021
1 parent 4701e89 commit 309c4bd
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 2 deletions.
1 change: 1 addition & 0 deletions docs/api/createAsyncThunk.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ An object with the following optional fields:

- `condition`: a callback that can be used to skip execution of the payload creator and all action dispatches, if desired. See [Canceling Before Execution](#canceling-before-execution) for a complete description.
- `dispatchConditionRejection`: if `condition()` returns `false`, the default behavior is that no actions will be dispatched at all. If you still want a "rejected" action to be dispatched when the thunk was canceled, set this flag to `true`.
- `idGenerator`: a function to use when generating the `requestId` for the request sequence. Defaults to use [nanoid](./otherExports.mdx/#nanoid).

## Return Value

Expand Down
2 changes: 1 addition & 1 deletion docs/api/otherExports.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Redux Toolkit exports some of its internal utilities, and re-exports additional

### `nanoid`

An inlined copy of [`nanoid/nonsecure`](https://github.com/ai/nanoid). Generates a non-cryptographically-secure random ID string. Automatically used by `createAsyncThunk` for request IDs, but may also be useful for other cases as well.
An inlined copy of [`nanoid/nonsecure`](https://github.com/ai/nanoid). Generates a non-cryptographically-secure random ID string. `createAsyncThunk` uses this by default for request IDs. May also be useful for other cases as well.

```ts
import { nanoid } from '@reduxjs/toolkit'
Expand Down
1 change: 1 addition & 0 deletions etc/redux-toolkit.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export type AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig extends AsyncThu
export interface AsyncThunkOptions<ThunkArg = void, ThunkApiConfig extends AsyncThunkConfig = {}> {
condition?(arg: ThunkArg, api: Pick<GetThunkAPI<ThunkApiConfig>, 'getState' | 'extra'>): boolean | undefined;
dispatchConditionRejection?: boolean;
idGenerator?: () => string;
// (undocumented)
serializeError?: (x: unknown) => GetSerializedErrorType<ThunkApiConfig>;
}
Expand Down
62 changes: 62 additions & 0 deletions src/createAsyncThunk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -680,3 +680,65 @@ describe('unwrapResult', () => {
await expect(unwrapPromise2.unwrap()).rejects.toBe('rejectWithValue!')
})
})

describe('idGenerator option', () => {
const getState = () => ({})
const dispatch = (x: any) => x
const extra = {}

test('idGenerator implementation - can customizes how request IDs are generated', async () => {
function makeFakeIdGenerator() {
let id = 0
return jest.fn(() => {
id++
return `fake-random-id-${id}`
})
}

let generatedRequestId = ''

const idGenerator = makeFakeIdGenerator()
const asyncThunk = createAsyncThunk(
'test',
async (args: void, { requestId }) => {
generatedRequestId = requestId
},
{ idGenerator }
)

// dispatching the thunks should be using the custom id generator
const promise0 = asyncThunk()(dispatch, getState, extra)
expect(generatedRequestId).toEqual('fake-random-id-1')
expect(promise0.requestId).toEqual('fake-random-id-1')
expect((await promise0).meta.requestId).toEqual('fake-random-id-1')

const promise1 = asyncThunk()(dispatch, getState, extra)
expect(generatedRequestId).toEqual('fake-random-id-2')
expect(promise1.requestId).toEqual('fake-random-id-2')
expect((await promise1).meta.requestId).toEqual('fake-random-id-2')

const promise2 = asyncThunk()(dispatch, getState, extra)
expect(generatedRequestId).toEqual('fake-random-id-3')
expect(promise2.requestId).toEqual('fake-random-id-3')
expect((await promise2).meta.requestId).toEqual('fake-random-id-3')

generatedRequestId = ''
const defaultAsyncThunk = createAsyncThunk(
'test',
async (args: void, { requestId }) => {
generatedRequestId = requestId
}
)
// dispatching the default options thunk should still generate an id,
// but not using the custom id generator
const promise3 = defaultAsyncThunk()(dispatch, getState, extra)
expect(generatedRequestId).toEqual(promise3.requestId)
expect(promise3.requestId).not.toEqual('')
expect(promise3.requestId).not.toEqual(
expect.stringContaining('fake-random-id')
)
expect((await promise3).meta.requestId).not.toEqual(
expect.stringContaining('fake-fandom-id')
)
})
})
9 changes: 8 additions & 1 deletion src/createAsyncThunk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,13 @@ export interface AsyncThunkOptions<
dispatchConditionRejection?: boolean

serializeError?: (x: unknown) => GetSerializedErrorType<ThunkApiConfig>

/**
* A function to use when generating the `requestId` for the request sequence.
*
* @default `nanoid`
*/
idGenerator?: () => string
}

export type AsyncThunkPendingActionCreator<
Expand Down Expand Up @@ -392,7 +399,7 @@ If you want to use the AbortController to react to \`abort\` events, please cons
arg: ThunkArg
): AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig> {
return (dispatch, getState, extra) => {
const requestId = nanoid()
const requestId = (options?.idGenerator ?? nanoid)()

const abortController = new AC()
let abortReason: string | undefined
Expand Down
24 changes: 24 additions & 0 deletions type-tests/files/createAsyncThunk.typetest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,3 +393,27 @@ const anyAction = { type: 'foo' } as AnyAction
expectType<Funky>(anyAction.error)
}
}

/**
* `idGenerator` option takes no arguments, and returns a string
*/
{
const returnsNumWithArgs = (foo: any) => 100
// has to stay on one line or type tests fail in older TS versions
// prettier-ignore
// @ts-expect-error
const shouldFailNumWithArgs = createAsyncThunk('foo', () => {}, { idGenerator: returnsNumWithArgs })

const returnsNumWithoutArgs = () => 100
// prettier-ignore
// @ts-expect-error
const shouldFailNumWithoutArgs = createAsyncThunk('foo', () => {}, { idGenerator: returnsNumWithoutArgs })

const returnsStrWithArgs = (foo: any) => 'foo'
// prettier-ignore
// @ts-expect-error
const shouldFailStrArgs = createAsyncThunk('foo', () => {}, { idGenerator: returnsStrWithArgs })

const returnsStrWithoutArgs = () => 'foo'
const shouldSucceed = createAsyncThunk('foo', () => {}, { idGenerator: returnsStrWithoutArgs })
}

0 comments on commit 309c4bd

Please sign in to comment.