Skip to content

Commit 16b9f23

Browse files
committed
change inference behaviour in old TS versions
1 parent a999dba commit 16b9f23

File tree

3 files changed

+79
-20
lines changed

3 files changed

+79
-20
lines changed

src/createAction.ts

Lines changed: 20 additions & 18 deletions
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
@@ -11,9 +12,9 @@ export type PayloadAction<
1112
P = any,
1213
T extends string = string,
1314
M = void
14-
> = Action<T> & {
15-
payload: P
16-
} & ([M] extends [void] ? {} : { meta: M })
15+
> = Action<T> & {
16+
payload: P
17+
} & ([M] extends [void] ? {} : { meta: M })
1718

1819
export type Diff<T, U> = T extends U ? never : T
1920

@@ -28,29 +29,30 @@ export type PayloadActionCreator<
2829
P = any,
2930
T extends string = string,
3031
PA extends PrepareAction<P> | void = void
31-
> = {
32-
type: T
33-
} & (PA extends (...args: any[]) => any
34-
? (ReturnType<PA> extends { meta: infer M }
32+
> = {
33+
type: T
34+
} & (PA extends (...args: any[]) => any
35+
? (ReturnType<PA> extends { meta: infer M }
3536
? (...args: Parameters<PA>) => PayloadAction<P, T, M>
3637
: (...args: Parameters<PA>) => PayloadAction<P, T>)
37-
: (/*
38+
: (/*
3839
* The `P` generic is wrapped with a single-element tuple to prevent the
3940
* conditional from being checked distributively, thus preserving unions
4041
* of contra-variant types.
4142
*/
42-
[undefined] extends [P]
43+
[undefined] extends [P]
4344
? {
44-
(payload?: undefined): PayloadAction<undefined, T>
45-
<PT extends Diff<P, undefined>>(payload?: PT): PayloadAction<PT, T>
46-
}
45+
(payload?: undefined): PayloadAction<undefined, T>
46+
<PT extends Diff<P, undefined>>(payload?: PT): PayloadAction<PT, T>
47+
}
4748
: [void] extends [P]
48-
? {
49-
(): PayloadAction<undefined, T>
50-
}
51-
: {
52-
<PT extends P>(payload: PT): PayloadAction<PT, T>
53-
}))
49+
? () => PayloadAction<undefined, T>
50+
: IsUnknownOrNonInferrable<P,
51+
// TS < 3.5 infers non-inferrable types to {}, which does not take `null`. This enforces `undefined` instead.
52+
<PT extends unknown>(payload: PT) => PayloadAction<PT, T>,
53+
// default behaviour
54+
<PT extends P>(payload: PT) => PayloadAction<PT, T>
55+
>))
5456

5557
/**
5658
* A utility function to create an action creator for the given action type

src/tsHelpers.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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> =
16+
T extends any
17+
? {} extends T
18+
? IsUnknown<T, False, IsAny<T, False, True>>
19+
: False
20+
: never;
21+
22+
23+
/**
24+
* returns True if TS version is above 3.5, False if below.
25+
* uses feature detection to detect TS version >= 3.5
26+
* * versions below 3.5 will return `{}` for unresolvable interference
27+
* * versions above will return `unknown`
28+
* */
29+
export type AtLeastTS35<True, False> = IsUnknown<ReturnType<<T>() => T>, True, False>;
30+
31+
export type IsUnknownOrNonInferrable<T, True, False> = AtLeastTS35<IsUnknown<T, True, False>, IsEmptyObj<T, True, False>>;

type-tests/files/createSlice.typetest.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,36 @@ function expectType<T>(t: T) {
153153
expectType<number>(counter.actions.concatMetaStrLen('test').meta)
154154

155155
// typings:expect-error
156-
expectType<string>(counter.actions.strLen('test').payload)
156+
expectType<string>(counter.actions.incrementByStrLen('test').payload)
157157

158158
// typings:expect-error
159-
expectType<string>(counter.actions.strLenMeta('test').meta)
159+
expectType<string>(counter.actions.concatMetaStrLen('test').meta)
160+
}
161+
162+
/*
163+
* Test: if no Payload Type is specified, accept any payload
164+
* see https://github.com/reduxjs/redux-starter-kit/issues/165
165+
*/
166+
{
167+
const initialState = {
168+
name: null
169+
};
170+
171+
172+
const mySlice = createSlice({
173+
initialState,
174+
reducers: {
175+
setName: (state, action) => {
176+
state.name = action.payload;
177+
}
178+
}
179+
});
180+
181+
const x = mySlice.actions.setName;
182+
183+
mySlice.actions.setName(null);
184+
mySlice.actions.setName("asd");
185+
mySlice.actions.setName(5);
160186
}
161187

162188
/*

0 commit comments

Comments
 (0)