Skip to content

Instantly share code, notes, and snippets.

@disintegrator
Created May 11, 2017 23:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save disintegrator/11ec5a70059b2fac578b10c127cd1963 to your computer and use it in GitHub Desktop.
Save disintegrator/11ec5a70059b2fac578b10c127cd1963 to your computer and use it in GitHub Desktop.
Redux TS boilerplate
export type ActionType = string;
export interface Action<T extends ActionType> {
type: T;
}
export interface BasicAction<T extends ActionType, P> extends Action<T> {
payload: P;
}
export interface ErrorAction<T extends ActionType> extends Action<T> {
payload: Error[];
error: true;
}
export interface BasicActionCreator<T extends ActionType, P> {
<Input>(payload?: Input): BasicAction<T, P>;
type: T;
}
export interface ErrorActionCreator<T extends ActionType> {
(payload: Error[]): ErrorAction<T>;
type: T;
}
export interface Dispatch<S> {
<A extends Action<ActionType>>(a: A): A;
}
declare interface ThunkActionCreator<T extends ActionType, Res> {
<Payload>(payload: Payload): <A>(dispatch: Dispatch<A>) => Promise<Res>;
states: {
request: T,
success: T,
failure: T,
}
}
export interface ServiceError {
id?: string;
title?: string;
detail: string;
code: string;
}
export const makeActionCreator = <P>(type: ActionType): BasicActionCreator<ActionType, P> =>
Object.assign((payload: P): BasicAction<ActionType, P> => ({
type,
payload,
}), { type });
export const makeErrorActionCreator = (type: ActionType): ErrorActionCreator<ActionType> =>
Object.assign((payload: Error[]): ErrorAction<ActionType> => ({
type,
payload,
error: true,
}), { type });
export interface AsyncWorker<Res> {
<Req>(dispatch: Dispatch<Res>, payload: Req): Promise<Res>;
}
export const makeAsyncActionCreator = <Res>(type: ActionType, worker: AsyncWorker<Res>): ThunkActionCreator<ActionType, Res> => {
const request = `${type}/request`;
const success = `${type}/success`;
const failure = `${type}/failure`;
const request$ = makeActionCreator<void>(request);
const success$ = makeActionCreator<Res>(success);
const failure$ = makeErrorActionCreator(failure);
const actionCreator = <Payload>(payload: Payload) => async (dispatch: Dispatch<Res>) => {
dispatch(request$());
try {
const res = await worker(dispatch, payload);
dispatch(success$(res));
return res;
} catch (err) {
dispatch(failure$(err));
throw err;
}
};
return Object.assign(
actionCreator,
{
type,
states: { request, success, failure }
}
);
}
export type AsyncActionState<P> = null
| { isFetching: true }
| { isFetching: false, payload: P }
| { isFetching: false, errors: Error[] }
const isErrorAction = <A extends ActionType, P>(action: BasicAction<A, P> | ErrorAction<A>): action is ErrorAction<A> => {
return (action as ErrorAction<A>).error === true;
}
export const makeAsyncReducer = <A extends ActionType, P>(actionCreator: ThunkActionCreator<A, P>) => {
const initialState = null;
const { request, success, failure } = actionCreator.states;
return (state: AsyncActionState<P> = initialState, action: BasicAction<A, P> | ErrorAction<A>): AsyncActionState<P> => {
switch (action.type) {
case request:
return { isFetching: true };
case success:
return isErrorAction<A, P>(action) ? state : { isFetching: false, payload: action.payload };
case failure:
return isErrorAction<A, P>(action) ? { errors: action.payload, isFetching: false } : state;
default:
return state;
}
};
};
const unexpected = (): ServiceError[] => [
{
detail: 'Something went wrong. Please try again.',
code: 'unexpected',
},
];
export const parseErrorPayload = (json: { errors?: ServiceError[] }) => ({
errors: typeof json.errors === 'undefined' ? unexpected() : json.errors
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment