diff --git a/src/createAction.ts b/src/createAction.ts index 208e1698bd..008b24b3a1 100644 --- a/src/createAction.ts +++ b/src/createAction.ts @@ -1,4 +1,5 @@ import { Action } from 'redux' +import { IsUnknownOrNonInferrable } from './tsHelpers' /** * An action with a string type and an associated payload. This is the @@ -46,7 +47,16 @@ export type ActionCreatorWithoutPayload< export type ActionCreatorWithPayload< P, T extends string = string -> = WithTypeProperty(payload: PT) => PayloadAction> +> = WithTypeProperty< + T, + IsUnknownOrNonInferrable< + P, + // TS < 3.5 infers non-inferrable types to {}, which does not take `null`. This enforces `undefined` instead. + (payload: PT) => PayloadAction, + // default behaviour + (payload: PT) => PayloadAction + > +> /** * An action creator that produces actions with a `payload` attribute. diff --git a/src/tsHelpers.ts b/src/tsHelpers.ts new file mode 100644 index 0000000000..b23dcd4207 --- /dev/null +++ b/src/tsHelpers.ts @@ -0,0 +1,36 @@ +// taken from https://github.com/joonhocho/tsdef +// return True if T is `any`, otherwise return False +export type IsAny = ( + | True + | False) extends (T extends never ? True : False) + ? True + : False + +// taken from https://github.com/joonhocho/tsdef +// return True if T is `unknown`, otherwise return False +export type IsUnknown = unknown extends T + ? IsAny + : False + +export type IsEmptyObj = T extends any + ? {} extends T + ? IsUnknown> + : False + : never + +/** + * returns True if TS version is above 3.5, False if below. + * uses feature detection to detect TS version >= 3.5 + * * versions below 3.5 will return `{}` for unresolvable interference + * * versions above will return `unknown` + * */ +export type AtLeastTS35 = IsUnknown< + ReturnType<() => T>, + True, + False +> + +export type IsUnknownOrNonInferrable = AtLeastTS35< + IsUnknown, + IsEmptyObj +> diff --git a/type-tests/files/createSlice.typetest.ts b/type-tests/files/createSlice.typetest.ts index d741a0f822..c0ff04b9b8 100644 --- a/type-tests/files/createSlice.typetest.ts +++ b/type-tests/files/createSlice.typetest.ts @@ -141,10 +141,10 @@ function expectType(t: T) { expectType(counter.actions.concatMetaStrLen('test').meta) // typings:expect-error - expectType(counter.actions.strLen('test').payload) + expectType(counter.actions.incrementByStrLen('test').payload) // typings:expect-error - expectType(counter.actions.strLenMeta('test').meta) + expectType(counter.actions.concatMetaStrLen('test').meta) } /* @@ -169,3 +169,30 @@ function expectType(t: T) { } }) } + + +/* + * Test: if no Payload Type is specified, accept any payload + * see https://github.com/reduxjs/redux-starter-kit/issues/165 + */ +{ + const initialState = { + name: null + }; + + + const mySlice = createSlice({ + initialState, + reducers: { + setName: (state, action) => { + state.name = action.payload; + } + } + }); + + const x = mySlice.actions.setName; + + mySlice.actions.setName(null); + mySlice.actions.setName("asd"); + mySlice.actions.setName(5); +} \ No newline at end of file