0
# Action Helpers
1
2
Utilities for type-safe action inspection and filtering, including type guards for discriminated union types and action creator type extraction for Redux/Flux architectures.
3
4
## Capabilities
5
6
### getType
7
8
Extracts the type literal from a given action creator, providing compile-time access to action type constants.
9
10
```typescript { .api }
11
/**
12
* Get the type literal of a given action creator
13
* @param actionCreator - Action creator with type metadata
14
* @returns Type constant from the action creator
15
*/
16
function getType<TType extends TypeConstant>(
17
actionCreator: ActionCreator<TType> & ActionCreatorTypeMetadata<TType>
18
): TType;
19
```
20
21
**Usage Examples:**
22
23
```typescript
24
import { createAction, getType } from "typesafe-actions";
25
26
const increment = createAction('INCREMENT')<number>();
27
const decrement = createAction('DECREMENT')();
28
29
// Extract type constants
30
const incrementType = getType(increment);
31
// Result: 'INCREMENT' (as literal type)
32
33
const decrementType = getType(decrement);
34
// Result: 'DECREMENT' (as literal type)
35
36
// Use in switch statements
37
function handleAction(action: Action) {
38
switch (action.type) {
39
case getType(increment):
40
// TypeScript knows this is increment action
41
console.log('Incrementing by:', action.payload);
42
break;
43
case getType(decrement):
44
// TypeScript knows this is decrement action
45
console.log('Decrementing');
46
break;
47
}
48
}
49
50
// Use for action type constants object
51
const ActionTypes = {
52
INCREMENT: getType(increment),
53
DECREMENT: getType(decrement),
54
} as const;
55
```
56
57
### isOfType
58
59
Type guard function to check if action type equals given type constant, works with discriminated union types and supports both curried and direct usage.
60
61
```typescript { .api }
62
/**
63
* Curried type guard to check if action type equals given type constant
64
* @param type - Action type constant or array of type constants
65
* @returns Curried function that takes action and returns type predicate
66
*/
67
function isOfType<T extends string>(
68
type: T | T[]
69
): <A extends { type: string }>(
70
action: A
71
) => action is A extends { type: T } ? A : never;
72
73
/**
74
* Direct type guard to check if action type equals given type constant
75
* @param type - Action type constant or array of type constants
76
* @param action - Action to check
77
* @returns Type predicate indicating if action matches type
78
*/
79
function isOfType<T extends string, A extends { type: string }>(
80
type: T | T[],
81
action: A
82
): action is A extends { type: T } ? A : never;
83
```
84
85
**Usage Examples:**
86
87
```typescript
88
import { isOfType, createAction } from "typesafe-actions";
89
90
const increment = createAction('INCREMENT')<number>();
91
const decrement = createAction('DECREMENT')();
92
const reset = createAction('RESET')();
93
94
type CounterAction =
95
| ReturnType<typeof increment>
96
| ReturnType<typeof decrement>
97
| ReturnType<typeof reset>;
98
99
// Curried usage - filter arrays
100
const actions: CounterAction[] = [
101
increment(5),
102
decrement(),
103
increment(3),
104
reset(),
105
];
106
107
const incrementActions = actions.filter(isOfType('INCREMENT'));
108
// Result: increment actions only, with proper typing
109
110
const counterActions = actions.filter(isOfType(['INCREMENT', 'DECREMENT']));
111
// Result: increment and decrement actions, with union typing
112
113
// Direct usage - type guards
114
function handleAction(action: CounterAction) {
115
if (isOfType(action, 'INCREMENT')) {
116
// TypeScript knows action.payload is number
117
console.log('Incrementing by:', action.payload);
118
} else if (isOfType(action, ['DECREMENT', 'RESET'])) {
119
// TypeScript knows action is decrement or reset
120
console.log('Decrementing or resetting');
121
}
122
}
123
124
// Epic/middleware usage
125
const incrementEpic = (action$: Observable<CounterAction>) =>
126
action$.pipe(
127
filter(isOfType('INCREMENT')),
128
// action is properly typed as increment action
129
map(action => console.log('Increment payload:', action.payload))
130
);
131
```
132
133
### isActionOf
134
135
Type guard function to check if action is instance of given action creator(s), works with discriminated union types and supports both curried and direct usage.
136
137
```typescript { .api }
138
/**
139
* Curried type guard to check if action is instance of given action creator
140
* @param actionCreator - Action creator to check against
141
* @returns Curried function that takes action and returns type predicate
142
*/
143
function isActionOf<AC extends ActionCreator<{ type: string }>>(
144
actionCreator: AC | AC[]
145
): (action: { type: string }) => action is ReturnType<AC>;
146
147
/**
148
* Direct type guard to check if action is instance of given action creator
149
* @param actionCreator - Action creator to check against
150
* @param action - Action to check
151
* @returns Type predicate indicating if action was created by action creator
152
*/
153
function isActionOf<AC extends ActionCreator<{ type: string }>>(
154
actionCreator: AC | AC[],
155
action: { type: string }
156
): action is ReturnType<AC>;
157
```
158
159
**Usage Examples:**
160
161
```typescript
162
import { isActionOf, createAction, createAsyncAction } from "typesafe-actions";
163
164
const increment = createAction('INCREMENT')<number>();
165
const decrement = createAction('DECREMENT')();
166
const reset = createAction('RESET')();
167
168
const fetchUser = createAsyncAction(
169
'FETCH_USER_REQUEST',
170
'FETCH_USER_SUCCESS',
171
'FETCH_USER_FAILURE'
172
)<void, User, Error>();
173
174
type AppAction =
175
| ReturnType<typeof increment>
176
| ReturnType<typeof decrement>
177
| ReturnType<typeof reset>
178
| ReturnType<typeof fetchUser.request>
179
| ReturnType<typeof fetchUser.success>
180
| ReturnType<typeof fetchUser.failure>;
181
182
// Curried usage - filter arrays
183
const actions: AppAction[] = [
184
increment(5),
185
fetchUser.request(),
186
decrement(),
187
fetchUser.success({ id: 1, name: 'Alice' }),
188
reset(),
189
];
190
191
const incrementActions = actions.filter(isActionOf(increment));
192
// Result: increment actions only, properly typed
193
194
const fetchActions = actions.filter(isActionOf([
195
fetchUser.request,
196
fetchUser.success,
197
fetchUser.failure
198
]));
199
// Result: all fetch-related actions, with union typing
200
201
// Direct usage - type guards
202
function handleAction(action: AppAction) {
203
if (isActionOf(action, increment)) {
204
// TypeScript knows action.payload is number
205
console.log('Incrementing by:', action.payload);
206
} else if (isActionOf(action, fetchUser.success)) {
207
// TypeScript knows action.payload is User
208
console.log('User fetched:', action.payload.name);
209
} else if (isActionOf(action, [fetchUser.request, fetchUser.failure])) {
210
// TypeScript knows action is request or failure
211
console.log('Fetch request or failure');
212
}
213
}
214
215
// Reducer usage
216
const userReducer = (state: UserState, action: AppAction): UserState => {
217
if (isActionOf(action, fetchUser.success)) {
218
return {
219
...state,
220
user: action.payload, // properly typed as User
221
loading: false,
222
};
223
}
224
225
if (isActionOf(action, [fetchUser.request])) {
226
return {
227
...state,
228
loading: true,
229
error: null,
230
};
231
}
232
233
return state;
234
};
235
236
// Epic/middleware usage
237
const fetchUserEpic = (action$: Observable<AppAction>) =>
238
action$.pipe(
239
filter(isActionOf(fetchUser.request)),
240
// action is properly typed as request action
241
switchMap(() =>
242
api.fetchUser().pipe(
243
map(user => fetchUser.success(user)),
244
catchError(error => of(fetchUser.failure(error)))
245
)
246
)
247
);
248
```
249
250
## Advanced Usage Patterns
251
252
### Combining with Type Guards
253
254
```typescript
255
import { isActionOf, isOfType, createAction } from "typesafe-actions";
256
257
const actions = [
258
createAction('SET_LOADING')<boolean>(),
259
createAction('SET_ERROR')<string>(),
260
createAction('CLEAR_STATE')(),
261
];
262
263
// Combine multiple type guards
264
function isLoadingOrError(action: Action) {
265
return isOfType(action, ['SET_LOADING', 'SET_ERROR']) ||
266
isActionOf(action, actions.slice(0, 2));
267
}
268
269
// Use in complex filtering
270
const relevantActions = allActions.filter(action =>
271
isActionOf(action, [actions[0], actions[1]]) &&
272
!isOfType(action, 'CLEAR_STATE')
273
);
274
```
275
276
### Epic and Middleware Integration
277
278
```typescript
279
import { Epic } from 'redux-observable';
280
import { isActionOf } from 'typesafe-actions';
281
282
// Type-safe epic with action filtering
283
const saveUserEpic: Epic<AppAction> = (action$) =>
284
action$.pipe(
285
filter(isActionOf([updateUser, createUser])),
286
// Actions are properly typed here
287
debounceTime(500),
288
switchMap(action =>
289
api.saveUser(action.payload).pipe(
290
map(() => saveUserSuccess()),
291
catchError(error => of(saveUserFailure(error)))
292
)
293
)
294
);
295
```
296
297
## Types
298
299
```typescript { .api }
300
/**
301
* ActionCreator type used by isActionOf (specialized version)
302
*/
303
type ActionCreator<T extends { type: string }> = ((
304
...args: any[]
305
) => T) & ActionCreatorTypeMetadata<T['type']>;
306
307
/**
308
* Type predicate function for checking action types
309
*/
310
type TypePredicate<T extends string> = <A extends { type: string }>(
311
action: A
312
) => action is A extends { type: T } ? A : never;
313
314
/**
315
* Type predicate function for checking action creators
316
*/
317
type ActionPredicate<AC extends ActionCreator<{ type: string }>> = (
318
action: { type: string }
319
) => action is ReturnType<AC>;
320
321
/**
322
* Utility type for extracting action type from action creator
323
*/
324
type ActionType<TActionCreatorOrMap extends any> =
325
TActionCreatorOrMap extends ActionCreator<infer TAction>
326
? TAction
327
: TActionCreatorOrMap extends Record<any, any>
328
? { [K in keyof TActionCreatorOrMap]: ActionType<TActionCreatorOrMap[K]> }[keyof TActionCreatorOrMap]
329
: never;
330
```