This is a draft idea to reduce boilerplate code when writing action creators for redux.
It simplifies the proces to bare minimum and provides shourtcuts for comminly used patterns like async
action
(where for one action you need to have 2 more that represent success or failure).
export interface NamespacedAsyncActionCreator {
(namespace: string): AsyncActionCreators;
}
export interface NamespacedActionCreator {
(namespace: string): ActionCreator;
}
export interface AsyncActionCreators {
createAsyncAction: <T = any, R = any, D = T, S = R>(
type: string,
payloadMainSetter?: PayloadSetter<T | undefined, D>,
payloadSuccessSetter?: PayloadSetter<R | undefined, S>,
) => AsyncActionObj<T, D, R, S>;
createAsyncActionWithPayload: <T = any, R = any, D = T, S = R>(
type: string,
payloadMainSetter?: PayloadSetter<T, D>,
payloadSuccessSetter?: PayloadSetter<R, S>,
) => AsyncActionWithPayloadObj<T, D, R, S>;
}
export interface PayloadSetter<T = any, D = any> {
(payload: T): D;
}
export interface TypedAction<T> {
type: string;
payload: T;
}
export interface AsyncActionObj<T = any, D = T, R = any, S = R> {
initial: ActionCreator<T, D>;
success: ActionCreator<R, S>;
failure: ActionWithPayloadCreator<string>;
}
export interface AsyncActionWithPayloadObj<T = any, D = T, R = any, S = R> {
initial: ActionWithPayloadCreator<T, D>;
success: ActionWithPayloadCreator<R, S>;
failure: ActionWithPayloadCreator<string>;
}
export interface ActionCreator<T = any, D = T> {
TYPE: string;
(payload?: T): TypedAction<D>;
}
export declare function getActionCreator<T = any, D = T>(type: string): ActionCreator<T, D>;
export interface ActionWithPayloadCreator<T = any, D = T> {
TYPE: string;
(payload: T): TypedAction<D>;
}
declare var getActionCreatorNs: NamespacedActionCreator;
declare var getAsyncActionCreatorNs: NamespacedAsyncActionCreator;
type TokenizedString = string[];
interface StringTokenizer {
(string: string): TokenizedString;
}
interface StringDeTokenizer {
(tokenizedString: TokenizedString): string;
}
interface StringOperation<T = any> {
(tokenizedString: TokenizedString, extras?: T): TokenizedString;
}
const stringToToken: StringTokenizer = str => str.split('');
const stringFromToken: StringDeTokenizer = token => token.join('');
function compose<T extends Function>(...fns: T[]): T {
return ((...args) => fns.reduce((res, fn) => fn(...args))) as any;
}
function stringTransformer(...operations: StringOperation[]) {
return (string: string) => stringFromToken(
operations.reduce(
(token, op) => op(token),
stringToToken(string))
);
}
const addSpaceToUpperLetter: StringOperation = token => token
.reduce(
(newToken, t) => [
...newToken,
...(t === t.toUpperCase() ? [' ', t] : [t])
],
[] as TokenizedString);
const toFirstUpperCase: StringOperation = token => token
.reduce(
(newToken, t, i, token) => [
...newToken,
...(i === 0
? [t.toUpperCase()]
: t === ' ' && !!token[i + 1]
? [' ', token[i + 1].toUpperCase()]
: [t])
],
[] as TokenizedString)
.filter((t, i, token) => t === ' ' || token[i - 1] !== ' ');
const spaceToUnderscore: StringOperation = token => token.map(
t => t === '' ? '_' : t);
const capitalize: StringOperation = token => token.map(t => t.toUpperCase());
const addSpaceToUpperAndFirstUpper = compose(
addSpaceToUpperLetter,
toFirstUpperCase);
const strSpacedAndCaps = stringTransformer(addSpaceToUpperAndFirstUpper);
function Action(nameSpace: string): PropertyDecorator {
return (target, propName) => {
// Convert propName from lowerCamelCase to First Upper Case
const actionType = strSpacedAndCaps(propName.toString());
// Append [nameSpace] to converted propName
const type = `[${nameSpace}] ${actionType}`;
// Set prop to result of call getActionCreator(convertedPropName)
if (delete target.constructor[propName]) {
Object.defineProperty(target.constructor, propName, {
enumerable: true,
configurable: true,
value: getActionCreator(type)
});
}
};
}
function AsyncAction(nameSpace: string): PropertyDecorator {
return (target, propName) => {
// Convert propName from lowerCamelCase to First Upper Case
const actionType = strSpacedAndCaps(propName.toString());
// Append [nameSpace] to converted propName
const type = `[${nameSpace}] ${actionType}`;
// Set prop to result of call createAction(convertedPropName)
if (delete target.constructor[propName]) {
Object.defineProperty(target.constructor, propName, {
enumerable: true,
configurable: true,
value: createAsyncAction(type)
});
}
};
}
function createActionNs(namespace: string): () => PropertyDecorator {
return () => Action(namespace);
}
function createAsyncActionNs(namespace: string): () => PropertyDecorator {
return () => AsyncAction(namespace);
}
const MyAction = createActionNs('MyNamespace');
const MyAsyncAction = createAsyncActionNs('MyNamespace');
export class MyActions {
@MyAsyncAction()
static getApplications: AsyncActionObj<number | undefined, string>;
@MyAction()
static someSingleAction: ActionCreator;
}
// These are autogenerated strings of action type
MyActions.getApplications.initial.TYPE; // [MyNamespace] Get Applications
MyActions.getApplications.success.TYPE; // [MyNamespace] Get Applications Success
MyActions.getApplications.failure.TYPE; // [MyNamespace] Get Applications Failure
MyActions.someSingleAction.TYPE; // [MyNamespace] Some Single Action
// These are action creator functions
MyActions.getApplications.initial();
MyActions.getApplications.success('result');
MyActions.getApplications.failure('error');
MyActions.someSingleAction();
const {
createAsyncAction,
} = getAsyncActionCreatorNs('MyNamespace');
const createAction = getActionCreatorNs('MyNamespace');
export class MyActions2 {
static getApplications = createAsyncAction<number | undefined, string>('Get Applications');
static someSingleAction = createAction('Some Single Action');
}
// These are autogenerated strings of action type
MyActions2.getApplications.initial.TYPE; // [MyNamespace] Get Applications
MyActions2.getApplications.success.TYPE; // [MyNamespace] Get Applications Success
MyActions2.getApplications.failure.TYPE; // [MyNamespace] Get Applications Failure
MyActions2.someSingleAction.TYPE; // [MyNamespace] Some Single Action
// These are action creator functions
MyActions2.getApplications.initial();
MyActions2.getApplications.success('result');
MyActions2.getApplications.failure('error');
MyActions2.someSingleAction();