UNPKG

redux-tiles

Version:

Library to create and easily compose redux pieces together in less verbose manner

182 lines (159 loc) 4.37 kB
import { Dispatch } from "redux"; import { createType, ensureArray } from "../helpers"; import { getTopReducer } 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"); } export function shouldBeFetched({ isPending, fetched, error }: any): boolean { // if it is pending, then we have to wait anyway if (isPending) { return false; } // in case it was not fetched yet, we have to fetch it for the first time if (fetched === false) { return true; } // and if error is not null or undefined, we have to re-fetch it again if (error != null) { return true; } return false; } 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 { isPending, fetched, error, data } = selectors.get( getState(), params ); const isFetchingNeeded: boolean = shouldBeFetched({ isPending, fetched, error }); if (!isFetchingNeeded) { return Promise.resolve({ data, error, isPending }); } } dispatch({ type: START, payload: { path }, isPending: true }); const promise: Promise<any> = fn({ params, dispatch, getState, ...middlewares }) .then((data: any) => { promisesStorage[getIdentificator] = undefined; return dispatch({ type: SUCCESS, payload: { path, data }, data, isPending: false }); }) .catch((error: any) => { promisesStorage[getIdentificator] = undefined; return dispatch({ error, type: FAILURE, payload: { path }, isPending: false }); }); 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, selector }: ISyncActionTypes): FnResult { return handleMiddleware( ({ dispatch, getState, ...middlewares }: any, params: any) => { const path: string[] | null = nesting ? nesting(params) : null; const getData = () => selector(getState(), params); const data = fn({ params, dispatch, getState, getData, ...middlewares }); return dispatch({ type: SET, payload: { path, data }, data }); } ); }