Skip to content

Commit ecc9bd3

Browse files
phryneasmarkerikson
authored andcommitted
change inference behaviour in old TS versions (#166)
1 parent 3a50fbc commit ecc9bd3

File tree

3 files changed

+76
-3
lines changed

3 files changed

+76
-3
lines changed

src/createAction.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Action } from 'redux'
2+
import { IsUnknownOrNonInferrable } from './tsHelpers'
23

34
/**
45
* An action with a string type and an associated payload. This is the
@@ -46,7 +47,16 @@ export type ActionCreatorWithoutPayload<
4647
export type ActionCreatorWithPayload<
4748
P,
4849
T extends string = string
49-
> = WithTypeProperty<T, <PT extends P>(payload: PT) => PayloadAction<PT, T>>
50+
> = WithTypeProperty<
51+
T,
52+
IsUnknownOrNonInferrable<
53+
P,
54+
// TS < 3.5 infers non-inferrable types to {}, which does not take `null`. This enforces `undefined` instead.
55+
<PT extends unknown>(payload: PT) => PayloadAction<PT, T>,
56+
// default behaviour
57+
<PT extends P>(payload: PT) => PayloadAction<PT, T>
58+
>
59+
>
5060

5161
/**
5262
* An action creator that produces actions with a `payload` attribute.

src/tsHelpers.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// taken from https://github.com/joonhocho/tsdef
2+
// return True if T is `any`, otherwise return False
3+
export type IsAny<T, True, False = never> = (
4+
| True
5+
| False) extends (T extends never ? True : False)
6+
? True
7+
: False
8+
9+
// taken from https://github.com/joonhocho/tsdef
10+
// return True if T is `unknown`, otherwise return False
11+
export type IsUnknown<T, True, False = never> = unknown extends T
12+
? IsAny<T, False, True>
13+
: False
14+
15+
export type IsEmptyObj<T, True, False = never> = T extends any
16+
? {} extends T
17+
? IsUnknown<T, False, IsAny<T, False, True>>
18+
: False
19+
: never
20+
21+
/**
22+
* returns True if TS version is above 3.5, False if below.
23+
* uses feature detection to detect TS version >= 3.5
24+
* * versions below 3.5 will return `{}` for unresolvable interference
25+
* * versions above will return `unknown`
26+
* */
27+
export type AtLeastTS35<True, False> = IsUnknown<
28+
ReturnType<<T>() => T>,
29+
True,
30+
False
31+
>
32+
33+
export type IsUnknownOrNonInferrable<T, True, False> = AtLeastTS35<
34+
IsUnknown<T, True, False>,
35+
IsEmptyObj<T, True, False>
36+
>

type-tests/files/createSlice.typetest.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,10 @@ function expectType<T>(t: T) {
141141
expectType<number>(counter.actions.concatMetaStrLen('test').meta)
142142

143143
// typings:expect-error
144-
expectType<string>(counter.actions.strLen('test').payload)
144+
expectType<string>(counter.actions.incrementByStrLen('test').payload)
145145

146146
// typings:expect-error
147-
expectType<string>(counter.actions.strLenMeta('test').meta)
147+
expectType<string>(counter.actions.concatMetaStrLen('test').meta)
148148
}
149149

150150
/*
@@ -169,3 +169,30 @@ function expectType<T>(t: T) {
169169
}
170170
})
171171
}
172+
173+
174+
/*
175+
* Test: if no Payload Type is specified, accept any payload
176+
* see https://github.com/reduxjs/redux-starter-kit/issues/165
177+
*/
178+
{
179+
const initialState = {
180+
name: null
181+
};
182+
183+
184+
const mySlice = createSlice({
185+
initialState,
186+
reducers: {
187+
setName: (state, action) => {
188+
state.name = action.payload;
189+
}
190+
}
191+
});
192+
193+
const x = mySlice.actions.setName;
194+
195+
mySlice.actions.setName(null);
196+
mySlice.actions.setName("asd");
197+
mySlice.actions.setName(5);
198+
}

0 commit comments

Comments
 (0)