redux-tiles
Version:
Library to create and easily compose redux pieces together in less verbose manner
182 lines (159 loc) • 4.37 kB
text/typescript
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
});
}
);
}