Skip to content

Commit db5eedd

Browse files
committed
separate creators and the actions/caseReducers they expose, to avoid footgun of CaseReducers seeming available to creator type
1 parent a4df06e commit db5eedd

File tree

5 files changed

+202
-264
lines changed

5 files changed

+202
-264
lines changed

packages/toolkit/src/asyncThunkCreator.ts

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,33 +17,30 @@ import type { Id } from './tsHelpers'
1717

1818
export type AsyncThunkCreatorExposes<
1919
State,
20-
CaseReducers extends CreatorCaseReducers<State>,
20+
Definition extends ReducerDefinition,
2121
> = {
22-
actions: {
23-
[ReducerName in keyof CaseReducers]: CaseReducers[ReducerName] extends AsyncThunkSliceReducerDefinition<
24-
State,
25-
infer ThunkArg,
26-
infer Returned,
27-
infer ThunkApiConfig
28-
>
29-
? AsyncThunk<Returned, ThunkArg, ThunkApiConfig>
30-
: never
31-
}
32-
caseReducers: {
33-
[ReducerName in keyof CaseReducers]: CaseReducers[ReducerName] extends AsyncThunkSliceReducerDefinition<
34-
State,
35-
any,
36-
any,
37-
any
38-
>
39-
? Id<
40-
Pick<
41-
Required<CaseReducers[ReducerName]>,
42-
'fulfilled' | 'rejected' | 'pending' | 'settled'
43-
>
22+
action: Definition extends AsyncThunkSliceReducerDefinition<
23+
State,
24+
infer ThunkArg,
25+
infer Returned,
26+
infer ThunkApiConfig
27+
>
28+
? AsyncThunk<Returned, ThunkArg, ThunkApiConfig>
29+
: never
30+
31+
caseReducer: Definition extends AsyncThunkSliceReducerDefinition<
32+
State,
33+
any,
34+
any,
35+
any
36+
>
37+
? Id<
38+
Pick<
39+
Required<Definition>,
40+
'fulfilled' | 'rejected' | 'pending' | 'settled'
4441
>
45-
: never
46-
}
42+
>
43+
: never
4744
}
4845

4946
export type AsyncThunkSliceReducers<

packages/toolkit/src/createSlice.ts

Lines changed: 93 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -38,114 +38,75 @@ export enum ReducerType {
3838
asyncThunk = 'asyncThunk',
3939
}
4040

41-
export type RegisteredReducerType = keyof SliceReducerCreators<
42-
any,
43-
any,
44-
any,
45-
any
46-
>
41+
export type RegisteredReducerType = keyof SliceReducerCreators<any, any, any>
4742

4843
export type ReducerDefinition<
4944
T extends RegisteredReducerType = RegisteredReducerType,
5045
> = {
5146
_reducerDefinitionType: T
5247
}
5348

54-
export type ReducerCreatorEntry<
55-
Create,
56-
Exposes extends {
57-
actions?: Record<string, unknown>
58-
caseReducers?: Record<string, unknown>
59-
} = {},
60-
> = {
61-
create: Create
62-
actions: Exposes extends { actions: NonNullable<unknown> }
63-
? Exposes['actions']
64-
: {}
65-
caseReducers: Exposes extends { caseReducers: NonNullable<unknown> }
66-
? Exposes['caseReducers']
67-
: {}
68-
}
69-
7049
export type CreatorCaseReducers<State> =
7150
| Record<string, ReducerDefinition>
7251
| SliceCaseReducers<State>
7352

7453
export interface SliceReducerCreators<
7554
State,
76-
CaseReducers extends CreatorCaseReducers<State>,
77-
Name extends string,
55+
SliceName extends string,
7856
ReducerPath extends string,
7957
> {
80-
[ReducerType.reducer]: ReducerCreatorEntry<
81-
{
82-
(
83-
caseReducer: CaseReducer<State, PayloadAction>,
84-
): CaseReducerDefinition<State, PayloadAction>
85-
<Payload = any>(
86-
caseReducer: CaseReducer<State, PayloadAction<Payload>>,
87-
): CaseReducerDefinition<State, PayloadAction<Payload>>
88-
},
89-
{
90-
actions: {
91-
[ReducerName in keyof CaseReducers]: CaseReducers[ReducerName] extends CaseReducer<
92-
State,
93-
any
94-
>
95-
? ActionCreatorForCaseReducer<
96-
CaseReducers[ReducerName],
97-
SliceActionType<Name, ReducerName>
98-
>
99-
: never
100-
}
101-
caseReducers: {
102-
[ReducerName in keyof CaseReducers]: CaseReducers[ReducerName] extends CaseReducer<
103-
State,
104-
any
105-
>
106-
? CaseReducers[ReducerName]
107-
: never
108-
}
109-
}
110-
>
111-
[ReducerType.reducerWithPrepare]: ReducerCreatorEntry<
112-
<Prepare extends PrepareAction<any>>(
113-
prepare: Prepare,
114-
reducer: CaseReducer<
115-
State,
116-
ReturnType<_ActionCreatorWithPreparedPayload<Prepare>>
117-
>,
118-
) => PreparedCaseReducerDefinition<State, Prepare>,
119-
{
120-
actions: {
121-
[ReducerName in keyof CaseReducers as ReducerName]: CaseReducers[ReducerName] extends CaseReducerWithPrepare<
122-
State,
123-
any
58+
[ReducerType.reducer]: {
59+
(
60+
caseReducer: CaseReducer<State, PayloadAction>,
61+
): CaseReducerDefinition<State, PayloadAction>
62+
<Payload = any>(
63+
caseReducer: CaseReducer<State, PayloadAction<Payload>>,
64+
): CaseReducerDefinition<State, PayloadAction<Payload>>
65+
}
66+
[ReducerType.reducerWithPrepare]: <Prepare extends PrepareAction<any>>(
67+
prepare: Prepare,
68+
reducer: CaseReducer<
69+
State,
70+
ReturnType<_ActionCreatorWithPreparedPayload<Prepare>>
71+
>,
72+
) => PreparedCaseReducerDefinition<State, Prepare>
73+
[ReducerType.asyncThunk]: AsyncThunkCreator<State>
74+
}
75+
76+
export interface SliceReducerCreatorsExposes<
77+
State,
78+
SliceName extends string,
79+
ReducerPath extends string,
80+
ReducerName extends PropertyKey,
81+
Definition extends ReducerDefinition,
82+
> {
83+
[creatorType: PropertyKey]: {
84+
action?: NonNullable<unknown>
85+
caseReducer?: NonNullable<unknown>
86+
}
87+
[ReducerType.reducer]: {
88+
action: Definition extends CaseReducer<State, any>
89+
? ActionCreatorForCaseReducer<
90+
Definition,
91+
SliceActionType<SliceName, ReducerName>
12492
>
125-
? CaseReducers[ReducerName] extends { prepare: any }
126-
? ActionCreatorForCaseReducerWithPrepare<
127-
CaseReducers[ReducerName],
128-
SliceActionType<Name, ReducerName>
129-
>
130-
: never
131-
: never
132-
}
133-
caseReducers: {
134-
[ReducerName in keyof CaseReducers]: CaseReducers[ReducerName] extends CaseReducerWithPrepare<
135-
State,
136-
any
93+
: never
94+
caseReducer: Definition extends CaseReducer<State, any> ? Definition : never
95+
}
96+
[ReducerType.reducerWithPrepare]: {
97+
action: Definition extends { prepare: any }
98+
? NonNullable<
99+
ActionCreatorForCaseReducerWithPrepare<
100+
Definition,
101+
SliceActionType<SliceName, ReducerName>
102+
>
137103
>
138-
? CaseReducers[ReducerName] extends { reducer: infer Reducer }
139-
? Reducer
140-
: never
141-
: never
142-
}
143-
}
144-
>
145-
[ReducerType.asyncThunk]: ReducerCreatorEntry<
146-
AsyncThunkCreator<State>,
147-
AsyncThunkCreatorExposes<State, CaseReducers>
148-
>
104+
: never
105+
caseReducer: Definition extends PreparedCaseReducerDefinition<State, any>
106+
? Definition['reducer']
107+
: never
108+
}
109+
[ReducerType.asyncThunk]: AsyncThunkCreatorExposes<State, Definition>
149110
}
150111

151112
export type ReducerCreators<
@@ -154,32 +115,24 @@ export type ReducerCreators<
154115
ReducerPath extends string = Name,
155116
CreatorMap extends Record<string, RegisteredReducerType> = {},
156117
> = {
157-
reducer: SliceReducerCreators<
158-
State,
159-
any,
160-
Name,
161-
ReducerPath
162-
>[ReducerType.reducer]['create']
118+
reducer: SliceReducerCreators<State, Name, ReducerPath>[ReducerType.reducer]
163119
preparedReducer: SliceReducerCreators<
164120
State,
165-
any,
166121
Name,
167122
ReducerPath
168-
>[ReducerType.reducerWithPrepare]['create']
123+
>[ReducerType.reducerWithPrepare]
169124
} & {
170125
[CreatorName in keyof CreatorMap as SliceReducerCreators<
171126
State,
172-
never,
173127
Name,
174128
ReducerPath
175-
>[CreatorMap[CreatorName]]['create'] extends never
129+
>[CreatorMap[CreatorName]] extends never
176130
? never
177131
: CreatorName]: SliceReducerCreators<
178132
State,
179-
never,
180133
Name,
181134
ReducerPath
182-
>[CreatorMap[CreatorName]]['create']
135+
>[CreatorMap[CreatorName]]
183136
}
184137

185138
interface InternalReducerHandlingContext<State> {
@@ -306,14 +259,14 @@ type DefinitionFromValue<
306259

307260
type ReducerDefinitionsForType<Type extends RegisteredReducerType> = {
308261
[CreatorType in RegisteredReducerType]: DefinitionFromValue<
309-
SliceReducerCreators<any, any, any, any>[CreatorType]['create'],
262+
SliceReducerCreators<any, any, any>[CreatorType],
310263
Type
311264
>
312265
}[RegisteredReducerType]
313266

314267
export type ReducerCreator<Type extends RegisteredReducerType> = {
315268
type: Type
316-
create: SliceReducerCreators<any, any, any, any>[Type]['create']
269+
create: SliceReducerCreators<any, any, any>[Type]
317270
} & (ReducerDefinitionsForType<Type> extends never
318271
? {
319272
handle?<State>(
@@ -637,9 +590,15 @@ export type SliceActionType<
637590
ActionName extends keyof any,
638591
> = ActionName extends string | number ? `${SliceName}/${ActionName}` : string
639592

640-
type ConvertNeverKeysToUnknown<T> = T extends any
641-
? { [K in keyof T]: T[K] extends never ? unknown : T[K] }
642-
: never
593+
type TagAnonymousDefinition<
594+
Definition extends CreatorCaseReducers<any>[string],
595+
> = Definition extends ReducerDefinition
596+
? Definition
597+
: Definition extends CaseReducer<any, any>
598+
? Definition & ReducerDefinition<ReducerType.reducer>
599+
: Definition extends CaseReducerWithPrepare<any, any>
600+
? Definition & ReducerDefinition<ReducerType.reducerWithPrepare>
601+
: never
643602

644603
/**
645604
* Derives the slice's `actions` property from the `reducers` options
@@ -651,18 +610,21 @@ export type CaseReducerActions<
651610
SliceName extends string,
652611
ReducerPath extends string = SliceName,
653612
State = any,
654-
> = Id<
655-
UnionToIntersection<
656-
ConvertNeverKeysToUnknown<
657-
SliceReducerCreators<
613+
> = Id<{
614+
[ReducerName in keyof CaseReducers]: TagAnonymousDefinition<
615+
CaseReducers[ReducerName]
616+
> extends ReducerDefinition<infer Type>
617+
? SliceReducerCreatorsExposes<
658618
State,
659-
CaseReducers,
660619
SliceName,
661-
ReducerPath
662-
>[RegisteredReducerType]['actions']
663-
>
664-
>
665-
>
620+
ReducerPath,
621+
ReducerName,
622+
TagAnonymousDefinition<CaseReducers[ReducerName]>
623+
> extends Record<Type, { action: infer Action extends {} }>
624+
? Action
625+
: never
626+
: never
627+
}>
666628

667629
/**
668630
* Get a `PayloadActionCreator` type for a passed `CaseReducerWithPrepare`
@@ -699,18 +661,21 @@ type SliceDefinedCaseReducers<
699661
SliceName extends string = string,
700662
ReducerPath extends string = SliceName,
701663
State = any,
702-
> = Id<
703-
UnionToIntersection<
704-
ConvertNeverKeysToUnknown<
705-
SliceReducerCreators<
664+
> = Id<{
665+
[ReducerName in keyof CaseReducers]: TagAnonymousDefinition<
666+
CaseReducers[ReducerName]
667+
> extends ReducerDefinition<infer Type>
668+
? SliceReducerCreatorsExposes<
706669
State,
707-
CaseReducers,
708670
SliceName,
709-
ReducerPath
710-
>[RegisteredReducerType]['caseReducers']
711-
>
712-
>
713-
>
671+
ReducerPath,
672+
ReducerName,
673+
TagAnonymousDefinition<CaseReducers[ReducerName]>
674+
> extends Record<Type, { caseReducer: infer CaseReducer }>
675+
? CaseReducer
676+
: never
677+
: never
678+
}>
714679

715680
type RemappedSelector<S extends Selector, NewState> =
716681
S extends Selector<any, infer R, infer P>

packages/toolkit/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ export type {
8383
ReducerCreators,
8484
SliceSelectors,
8585
SliceReducerCreators,
86+
SliceReducerCreatorsExposes,
8687
ReducerDefinition,
87-
ReducerCreatorEntry,
8888
ReducerCreator,
8989
ReducerDetails,
9090
ReducerHandlingContext,

0 commit comments

Comments
 (0)