Skip to content

use ThunkApiConfig for optional type arguments #364

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion etc/redux-toolkit.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { ReducersMapObject } from 'redux';
import { Store } from 'redux';
import { StoreEnhancer } from 'redux';
import { ThunkAction } from 'redux-thunk';
import { ThunkDispatch } from 'redux-thunk';
import { ThunkMiddleware } from 'redux-thunk';

// @public
Expand Down Expand Up @@ -102,7 +103,7 @@ export function createAction<P = void, T extends string = string>(type: T): Payl
export function createAction<PA extends PrepareAction<any>, T extends string = string>(type: T, prepareAction: PA): PayloadActionCreator<ReturnType<PA>['payload'], T, PA>;

// @alpha (undocumented)
export function createAsyncThunk<Returned, ThunkArg = void, State = unknown, Extra = unknown, DispatchType extends Dispatch = Dispatch, ActionType extends string = string, ThunkAPI extends BaseThunkAPI<any, any, any> = BaseThunkAPI<State, Extra, DispatchType>>(type: ActionType, payloadCreator: (arg: ThunkArg, thunkAPI: ThunkAPI) => Promise<Returned> | Returned): ((arg: ThunkArg) => (dispatch: ThunkAPI["dispatch"], getState: ThunkAPI["getState"], extra: ThunkAPI["extra"]) => Promise<PayloadAction<undefined, string, {
export function createAsyncThunk<Returned, ThunkArg = void, ThunkApiConfig extends AsyncThunkConfig = {}>(type: string, payloadCreator: (arg: ThunkArg, thunkAPI: GetThunkAPI<ThunkApiConfig>) => Promise<Returned> | Returned): ((arg: ThunkArg) => (dispatch: GetDispatch<ThunkApiConfig>, getState: () => GetState<ThunkApiConfig>, extra: GetExtra<ThunkApiConfig>) => Promise<PayloadAction<undefined, string, {
arg: ThunkArg;
requestId: string;
aborted: boolean;
Expand Down
7 changes: 4 additions & 3 deletions src/combinedTest.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Dispatch } from 'redux'
import { createAsyncThunk, BaseThunkAPI } from './createAsyncThunk'
import { createAsyncThunk } from './createAsyncThunk'
import { createAction, PayloadAction } from './createAction'
import { createSlice } from './createSlice'
import { configureStore } from './configureStore'
Expand Down Expand Up @@ -38,7 +37,9 @@ describe('Combined entity slice', () => {
const fetchBooksTAC = createAsyncThunk<
BookModel[],
void,
{ books: BooksState }
{
state: { books: BooksState }
}
>(
'books/fetch',
async (arg, { getState, dispatch, extra, requestId, signal }) => {
Expand Down
59 changes: 43 additions & 16 deletions src/createAsyncThunk.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Dispatch } from 'redux'
import { Dispatch, AnyAction } from 'redux'
import nanoid from 'nanoid'
import {
createAction,
PayloadAction,
ActionCreatorWithPreparedPayload
} from './createAction'
import { ThunkDispatch } from 'redux-thunk'
import { FallbackIfUnknown } from './tsHelpers'

// @ts-ignore we need the import of these types due to a bundling issue.
type _Keep = PayloadAction | ActionCreatorWithPreparedPayload<any, unknown>
Expand Down Expand Up @@ -50,6 +52,39 @@ export const miniSerializeError = (value: any): any => {
return value
}

type AsyncThunkConfig = {
state?: unknown
dispatch?: Dispatch
extra?: unknown
}

type GetState<ThunkApiConfig> = ThunkApiConfig extends {
state: infer State
}
? State
: unknown
type GetExtra<ThunkApiConfig> = ThunkApiConfig extends { extra: infer Extra }
? Extra
: unknown
type GetDispatch<ThunkApiConfig> = ThunkApiConfig extends {
dispatch: infer Dispatch
}
? FallbackIfUnknown<
Dispatch,
ThunkDispatch<
GetState<ThunkApiConfig>,
GetExtra<ThunkApiConfig>,
AnyAction
>
>
: ThunkDispatch<GetState<ThunkApiConfig>, GetExtra<ThunkApiConfig>, AnyAction>

type GetThunkAPI<ThunkApiConfig> = BaseThunkAPI<
GetState<ThunkApiConfig>,
GetExtra<ThunkApiConfig>,
GetDispatch<ThunkApiConfig>
>

/**
*
* @param type
Expand All @@ -60,20 +95,12 @@ export const miniSerializeError = (value: any): any => {
export function createAsyncThunk<
Returned,
ThunkArg = void,
State = unknown,
Extra = unknown,
DispatchType extends Dispatch = Dispatch,
ActionType extends string = string,
ThunkAPI extends BaseThunkAPI<any, any, any> = BaseThunkAPI<
State,
Extra,
DispatchType
>
ThunkApiConfig extends AsyncThunkConfig = {}
>(
type: ActionType,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed this type completely since it does not seem to have any value - all action types are concatenation to that string, so it'll never be used again.

type: string,
payloadCreator: (
arg: ThunkArg,
thunkAPI: ThunkAPI
thunkAPI: GetThunkAPI<ThunkApiConfig>
) => Promise<Returned> | Returned
) {
const fulfilled = createAction(
Expand Down Expand Up @@ -115,9 +142,9 @@ export function createAsyncThunk<

function actionCreator(arg: ThunkArg) {
return (
dispatch: ThunkAPI['dispatch'],
getState: ThunkAPI['getState'],
extra: ThunkAPI['extra']
dispatch: GetDispatch<ThunkApiConfig>,
getState: () => GetState<ThunkApiConfig>,
extra: GetExtra<ThunkApiConfig>
) => {
const requestId = nanoid()
const abortController = new AbortController()
Expand Down Expand Up @@ -145,7 +172,7 @@ export function createAsyncThunk<
extra,
requestId,
signal: abortController.signal
} as ThunkAPI),
}),
requestId,
arg
)
Expand Down
2 changes: 2 additions & 0 deletions src/tsHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export type IsUnknown<T, True, False = never> = unknown extends T
? IsAny<T, False, True>
: False

export type FallbackIfUnknown<T, Fallback> = IsUnknown<T, Fallback, T>

/**
* @internal
*/
Expand Down
24 changes: 17 additions & 7 deletions type-tests/files/createAsyncThunk.typetest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@ import { unwrapResult } from 'src/createAsyncThunk'
function expectType<T>(t: T) {
return t
}
function fn() {}
const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, AnyAction>

// basic usage
// basic usage
;(async function() {
const dispatch = fn as ThunkDispatch<{}, any, AnyAction>

const async = createAsyncThunk('test', (id: number) =>
Promise.resolve(id * 2)
)
Expand All @@ -31,7 +29,7 @@ function fn() {}
})
)

const promise = dispatch(async(3))
const promise = defaultDispatch(async(3))
const result = await promise

if (async.fulfilled.match(result)) {
Expand Down Expand Up @@ -70,12 +68,20 @@ function fn() {}
{ id: 'a', title: 'First' }
]

const correctDispatch = (() => {}) as ThunkDispatch<
BookModel[],
{ userAPI: Function },
AnyAction
>

// Verify that the the first type args to createAsyncThunk line up right
const fetchBooksTAC = createAsyncThunk<
BookModel[],
number,
BooksState,
{ userAPI: Function }
{
state: BooksState
extra: { userAPI: Function }
}
>(
'books/fetch',
async (arg, { getState, dispatch, extra, requestId, signal }) => {
Expand All @@ -87,4 +93,8 @@ function fn() {}
return fakeBooks
}
)

correctDispatch(fetchBooksTAC(1))
// typings:expect-error
defaultDispatch(fetchBooksTAC(1))
})()