redux-tiles
Version:
Library to create and easily compose redux pieces together in less verbose manner
116 lines (95 loc) • 3.46 kB
text/typescript
import { Dispatch } from 'redux';
import { createType } from '../helpers';
import { DEFAULT_REDUCER } from './selectors';
import { IAsyncActionTypes, IPromiseObject, ISyncActionTypes } from './types';
interface IProcessedMiddleware {
dispatch: Dispatch<{}>;
getState(): {};
[key: string]: any;
}
export type FnResult = (params: any, additionalParams?: any) => any;
function proccessMiddleware(args: any[]): IProcessedMiddleware {
if (args.length === 3) {
// let's assume it is redux-thunk with extra argument
return { dispatch: args[0], getState: args[1], ...args[2] };
} else if (args.length === 2) {
// likely it is redux-thunk
return { dispatch: args[0], getState: args[1] };
} else if (args.length === 1 && typeof args[0] === 'object') {
// our own middleware
return args[0];
}
// no idea what it is
throw new Error('Redux-Tiles expects own middleware, or redux-thunk');
}
function shouldBeFetched({ getState, selectors, params }: any): boolean {
const { isPending, data, error } = selectors.get(getState(), params);
// == intentionally to check on empty objects
return error == null && data == null && isPending !== true;
}
function handleMiddleware(fn: Function): FnResult {
return (fnParams: any, additionalParams: any): Function => (...args: any[]): any =>
fn(proccessMiddleware(args), fnParams, additionalParams);
}
export function asyncAction({
START, SUCCESS, FAILURE, fn, type, caching, nesting, selectors
}: IAsyncActionTypes): FnResult {
return handleMiddleware((
{ dispatch, getState, promisesStorage = {}, ...middlewares }:
{ dispatch: Dispatch<{}>, promisesStorage: IPromiseObject, getState(): {} },
params: any,
{ forceAsync }: { forceAsync?: boolean } = {}
) => {
const path: string[]|null = nesting ? nesting(params) : null;
const getIdentificator: string = createType({ type, path });
if (caching) {
const activePromise: Promise<any>|undefined = promisesStorage[getIdentificator];
if (activePromise) {
return activePromise;
}
}
if (caching && !forceAsync) {
const isFetchingNeeded: boolean = shouldBeFetched({ getState, selectors, params });
if (!isFetchingNeeded) {
return Promise.resolve();
}
}
dispatch({
type: START,
payload: { path }
});
const promise: Promise<any> = fn({ params, dispatch, getState, ...middlewares })
.then((data: any) => {
dispatch({
type: SUCCESS,
payload: { path, data }
});
promisesStorage[getIdentificator] = undefined;
})
.catch((error: any) => {
dispatch({
error,
type: FAILURE,
payload: { path }
});
promisesStorage[getIdentificator] = undefined;
});
promisesStorage[getIdentificator] = promise;
return promise;
});
}
export function createResetAction({ type }: { type: string }): Function {
return handleMiddleware(({ dispatch }: { dispatch: Dispatch<any> }) => dispatch({ type }));
}
export function syncAction({ SET, fn, nesting }: ISyncActionTypes): FnResult {
return handleMiddleware(({ dispatch, getState, ...middlewares }: any, params: any) => {
const path: string[]|null = nesting ? nesting(params) : null;
return dispatch({
type: SET,
payload: {
path,
data: fn({ params, dispatch, getState, ...middlewares })
}
});
});
}