Typesafe Action Creators for Redux / Flux Architectures in TypeScript
—
Complete TypeScript type definitions and interfaces for building type-safe Redux applications with action creators, reducers, and state management utilities.
Basic type definitions that form the foundation of the type system.
/**
* Type representing action type constants (string literals)
*/
type TypeConstant = string;
/**
* Generic action interface with type property
*/
interface Action<TType extends TypeConstant = TypeConstant> {
type: TType;
}
/**
* Generic action creator function type
*/
type ActionCreator<TAction extends Action = Action> = (
...args: any[]
) => TAction;
/**
* Generic reducer function type
*/
type Reducer<TState, TAction extends Action> = (
state: TState | undefined,
action: TAction
) => TState;Specialized action types for different payload and meta combinations.
/**
* Action without payload (type only)
*/
type EmptyAction<TType extends TypeConstant> = Action<TType>;
/**
* Action with payload property
*/
interface PayloadAction<TType extends TypeConstant, TPayload> extends Action<TType> {
payload: TPayload;
}
/**
* Action with both payload and meta properties
*/
interface PayloadMetaAction<TType extends TypeConstant, TPayload, TMeta>
extends PayloadAction<TType, TPayload> {
meta: TMeta;
}Specialized action creator types corresponding to different action types.
/**
* Action creator that produces EmptyAction
*/
type EmptyActionCreator<TType extends TypeConstant> = ActionCreator<EmptyAction<TType>>;
/**
* Action creator that produces PayloadAction
*/
type PayloadActionCreator<TType extends TypeConstant, TPayload> =
ActionCreator<PayloadAction<TType, TPayload>>;
/**
* Action creator that produces PayloadMetaAction
*/
type PayloadMetaActionCreator<TType extends TypeConstant, TPayload, TMeta> =
ActionCreator<PayloadMetaAction<TType, TPayload, TMeta>>;Interface for action creators with type metadata support.
/**
* Interface representing type getter on action creator instance
*/
interface ActionCreatorTypeMetadata<TType extends TypeConstant> {
getType?(): TType;
}Advanced utility types for type inference and manipulation.
/**
* Infers action union type from action creator or action creator map
*/
type ActionType<TActionCreatorOrMap extends any> =
TActionCreatorOrMap extends ActionCreator<TypeConstant>
? ReturnType<TActionCreatorOrMap>
: TActionCreatorOrMap extends Record<any, any>
? { [K in keyof TActionCreatorOrMap]: ActionType<TActionCreatorOrMap[K]> }[keyof TActionCreatorOrMap]
: never;
/**
* Infers state object type from reducer or reducer map
*/
type StateType<TReducerOrMap extends any> =
TReducerOrMap extends Reducer<any, any>
? ReturnType<TReducerOrMap>
: TReducerOrMap extends Record<any, any>
? { [K in keyof TReducerOrMap]: StateType<TReducerOrMap[K]> }
: never;Types used internally by action creator builders.
/**
* Action builder type for createAction results
*/
type ActionBuilder<TType extends TypeConstant, TPayload = undefined, TMeta = undefined> =
TPayload extends undefined
? TMeta extends undefined
? EmptyAction<TType>
: never
: TMeta extends undefined
? PayloadAction<TType, TPayload>
: PayloadMetaAction<TType, TPayload, TMeta>;
/**
* Action creator builder interface for createAction
*/
interface ActionCreatorBuilder<TType extends TypeConstant, TPayload = undefined, TMeta = undefined> {
(): TPayload extends undefined
? TMeta extends undefined
? () => EmptyAction<TType>
: never
: never;
<P>(): TMeta extends undefined
? (payload: P) => PayloadAction<TType, P>
: never;
<P, M>(): (payload: P, meta: M) => PayloadMetaAction<TType, P, M>;
map<R, P>(
fn: (payload: P) => R
): TPayload extends undefined
? ActionCreatorBuilder<TType, R, TMeta>
: never;
}
/**
* Async action creator builder for createAsyncAction
*/
interface AsyncActionCreatorBuilder<
TRequestType extends TypeConstant,
TSuccessType extends TypeConstant,
TFailureType extends TypeConstant,
TCancelType extends TypeConstant = never
> {
request: ActionCreatorBuilder<TRequestType>;
success: ActionCreatorBuilder<TSuccessType>;
failure: ActionCreatorBuilder<TFailureType>;
cancel: TCancelType extends TypeConstant
? ActionCreatorBuilder<TCancelType>
: never;
}Interface for augmenting the library with custom types.
/**
* Interface for module augmentation - extend this in your app
* @example
* ```
* declare module 'typesafe-actions' {
* export type RootAction = ActionType<typeof import('./root-action').default>;
* export interface Types {
* RootAction: RootAction;
* }
* }
* ```
*/
interface Types {
RootAction: Action;
}import type {
Action,
ActionCreator,
PayloadAction,
EmptyAction
} from "typesafe-actions";
// Define action types
type IncrementAction = PayloadAction<'INCREMENT', number>;
type DecrementAction = EmptyAction<'DECREMENT'>;
type CounterAction = IncrementAction | DecrementAction;
// Define action creators with proper typing
const increment: ActionCreator<IncrementAction> = (payload: number) => ({
type: 'INCREMENT',
payload,
});
const decrement: ActionCreator<DecrementAction> = () => ({
type: 'DECREMENT',
});import { createAction, createReducer } from "typesafe-actions";
import type { ActionType, StateType } from "typesafe-actions";
// Create action creators
const counterActions = {
increment: createAction('INCREMENT')<number>(),
decrement: createAction('DECREMENT')(),
reset: createAction('RESET')(),
};
// Infer action union type
type CounterAction = ActionType<typeof counterActions>;
// Result: ReturnType<typeof increment> | ReturnType<typeof decrement> | ReturnType<typeof reset>
// Create reducers
const rootReducer = {
counter: createReducer({ count: 0 })
.handleAction(counterActions.increment, (state, action) => ({
count: state.count + action.payload,
}))
.handleAction(counterActions.decrement, (state) => ({
count: state.count - 1,
}))
.handleAction(counterActions.reset, () => ({ count: 0 })),
user: createReducer({ name: '', email: '' })
.handleAction(setUser, (state, action) => action.payload),
};
// Infer root state type
type RootState = StateType<typeof rootReducer>;
// Result: { counter: { count: number }, user: { name: string, email: string } }// In your application's type definitions file
import { ActionType } from 'typesafe-actions';
declare module 'typesafe-actions' {
interface Types {
RootAction: ActionType<typeof rootActions>;
}
}
// Now you can use RootAction throughout your app
import type { RootAction } from 'typesafe-actions';
function handleAction(action: RootAction) {
// action is typed as your app's root action union
}import type {
ActionCreatorBuilder,
AsyncActionCreatorBuilder,
ActionCreatorTypeMetadata
} from "typesafe-actions";
// Generic action creator factory
function createTypedAction<T extends string>(type: T) {
return <P = undefined, M = undefined>(): ActionCreatorBuilder<T, P, M> => {
// Implementation would go here
return {} as ActionCreatorBuilder<T, P, M>;
};
}
// Generic async action factory
function createTypedAsyncAction<
R extends string,
S extends string,
F extends string
>(requestType: R, successType: S, failureType: F) {
return <RP = undefined, SP = undefined, FP = undefined>():
AsyncActionCreatorBuilder<R, S, F> => {
// Implementation would go here
return {} as AsyncActionCreatorBuilder<R, S, F>;
};
}
// Usage with full type inference
const typedAction = createTypedAction('TYPED_ACTION')<string, number>();
const result = typedAction('payload', 42);
// result is typed as PayloadMetaAction<'TYPED_ACTION', string, number>import type { Action, PayloadAction, EmptyAction } from "typesafe-actions";
// Custom type guards
function isPayloadAction<T extends string, P>(
action: Action,
type: T
): action is PayloadAction<T, P> {
return action.type === type && 'payload' in action;
}
function isEmptyAction<T extends string>(
action: Action,
type: T
): action is EmptyAction<T> {
return action.type === type && !('payload' in action);
}
// Usage in reducers
function counterReducer(state = { count: 0 }, action: Action) {
if (isPayloadAction<'INCREMENT', number>(action, 'INCREMENT')) {
// TypeScript knows action.payload is number
return { count: state.count + action.payload };
}
if (isEmptyAction(action, 'DECREMENT')) {
// TypeScript knows action has no payload
return { count: state.count - 1 };
}
return state;
}These types are used internally by the library but may be useful for advanced usage:
/**
* Internal type for resolving types - ensures proper type resolution in action creators
*/
type ResolveType<T> = T extends (...args: any[]) => any ? ReturnType<T> : T;
/**
* Internal utility for excluding never types from unions
*/
type ExcludeNever<T> = T extends never ? never : T;Install with Tessl CLI
npx tessl i tessl/npm-typesafe-actions