state-decorator
Version:
React state management library
214 lines (213 loc) • 9.5 kB
TypeScript
import { ConflictPolicy, DecoratedActions, NotifyFunc, PromiseResult } from './types';
export type PromiseProvider<S, F extends (...args: any[]) => any, A, P> = (args: Parameters<F>, state: S, props: P, actions: A, abortSignal: AbortSignal) => ReturnType<F> | null;
/**
* Simple form of a synchronous action.
*/
export type SynchAction<S, F extends (...args: any[]) => any, P> = (state: S, args: Parameters<F>, props: P) => S | null;
/**
* Advanced synchronous action.
* Allows to set advanced properties to a synchronous action.
*/
export type AdvancedSynchAction<S, F extends (...args: any[]) => any, A, P> = {
/**
* The action to execute.
*/
action: (state: S, args: Parameters<F>, props: P) => S | null;
/**
* Debounces the side effects if this parameter is defined.
*/
debounceSideEffectsTimeout?: number;
/**
* Debounces the action if this parameter is defined.
*/
debounceTimeout?: number;
/**
* Action to call when the action is done. Used to trigger other actions (even asynchronous),
*/
onActionDone?: (state: S, args: Parameters<F>, props: P, actions: A, notifyWarning: NotifyFunc) => void;
};
export type PromiseIdMap = {
[promiseId: string]: boolean;
};
export type InternalLoadingMap<A> = {
[name: string]: undefined | boolean | PromiseIdMap;
};
export type LoadingMap<A> = {
[P in keyof A]?: undefined | boolean;
};
export type LoadingMapParallelActions<A> = {
[P in keyof A]?: PromiseIdMap;
};
export type LoadingProps<A> = {
loading: boolean;
loadingMap: LoadingMap<A>;
loadingParallelMap: LoadingMapParallelActions<A>;
};
/**
* Try to abort an action, if the action is <code>abortable</code>.
* @param actionName The action name
* @param promiseId If the action conflict type is <code>ConflictType.PARALLEL</code>, the identifier of the promise.
* @returns <code>true</code> if the action is abortable and an action is ongoing, <code>false</code> otherwise.
*/
export type AbortActionCallback<A> = (actionName: keyof A, promiseId?: string) => boolean;
export type GetErrorMessage<F extends (...args: any[]) => any, P> = (e: any, args: Parameters<F>, props: P) => string;
export interface AsynchActionBase<S, F extends (...args: any[]) => any, A, P> {
/**
* This action can be aborted. An abortSignal will be injected to the <code>promise</code> / <code>promiseGet</code>.
*/
abortable?: boolean;
/**
* The success message to pass to the notifySuccess function passed as property to the StateDecorator.
*/
successMessage?: string;
/**
* A function that provides the success message to pass to the notifySuccess function passed as property to the StateDecorator.
*/
getSuccessMessage?: (result: PromiseResult<ReturnType<F>>, args: Parameters<F>, props: P) => string;
/**
* The error message to pass to the notifyError function passed as property to the StateDecorator.
*/
errorMessage?: string;
/**
* A function that provides the error message to pass to the notifyError function passed as property to the StateDecorator.
*/
getErrorMessage?: GetErrorMessage<F, P>;
/**
* If set, called with the result of the promise to update the current state.
*/
reducer?: (state: S, result: PromiseResult<ReturnType<F>>, args: Parameters<F>, props: P) => S | null;
/**
* If set, called with the error of the promise to update the current state.
*/
errorReducer?: (state: S, error: any, args: Parameters<F>, props: P) => S | null;
/**
* Whether reject the promise instead of handling it.
*/
rejectPromiseOnError?: boolean;
/**
* Handle side effects when the request succeeded.
* @param result The result of the request
* @param newData The data after the reducer is applied
* @param args The argument during the call of the request function
* @param props The props passed to the state decorator
* @param actions The actions available
* @param notifyWarning If specified, a notify function to indicate that the action is a semi success (use successMessage / getSuccessMessage otherwise).
*/
onDone?: (state: S, result: PromiseResult<ReturnType<F>>, args: Parameters<F>, props: P, actions: A, notifyWarning: NotifyFunc) => void;
/**
* Handle side effects when the request failed.
* @param error The error of the request
* @param args The argument during the call of the request function
* @param props The props passed to the state decorator
* @param actions The actions available
* @param notifyWarning If specified, a notify function to indicate that the action is a semi failure (use errorMessage / getErrorMessage otherwise).
*/
onFail?: (state: S, error: Error, args: Parameters<F>, props: P, actions: A, notifyWarning: NotifyFunc) => void;
/**
* Retrieve the state to set as current data before the promise is resolved.
* If the there's no reducer, this data will be used after the promise is done.
*/
preReducer?: (state: S, args: Parameters<F>, props: P) => S | null;
/**
* Retrieve the state to set as current data before the promise is resolved.
* If the there's no reducer, this data will be used after the promise is done.
*/
optimisticReducer?: (state: S, args: Parameters<F>, props: P) => S | null;
/**
* Policy to apply when a call to an asynchronous action is done but a previous call is still not resolved.
*/
conflictPolicy?: ConflictPolicy;
/**
* Get an identifier for an action call. Used only when conflictPolicy is ConflictPolicy.PARALLEL.
* This information will be available in loadingMap. loadingMap[actionName] will be an array of promise identifiers.
*/
getPromiseId?: (...args: Parameters<F>) => string;
/**
* Number of retries in case of error (failed to fetch).
* Do not use this if you are creating objects...
* Default value is 0 (no retry).
*/
retryCount?: number;
/**
* Seed of delay between each retry in milliseconds.
* The applied delay is retryDelaySeed * retry count.
* Default value is 1000 (1 second).
*/
retryDelaySeed?: number;
/**
* Function to test if the error will trigger an action retry or will fail directly.
*/
isTriggerRetryError?: (e: Error) => boolean;
}
export interface AsynchActionPromise<S, F extends (...args: any[]) => any, A, P> extends AsynchActionBase<S, F, A, P> {
/**
* The request, returns a promise
*/
promise: PromiseProvider<S, F, A, P>;
}
export interface AsynchActionPromiseGet<S, F extends (...args: any[]) => any, A, P> extends AsynchActionBase<S, F, A, P> {
/**
* The request, returns a promise that is a GET request.
* A shortcut to set:
* - retryCount: 3
* - conflictPolicy: ConflictPolicy.REUSE
*/
promiseGet: PromiseProvider<S, F, A, P>;
}
export type AsynchAction<S, F extends (...args: any[]) => any, A, P> = AsynchActionPromise<S, F, A, P> | AsynchActionPromiseGet<S, F, A, P>;
export type StateDecoratorAction<S, F extends (...args: any[]) => any, A, P> = AsynchAction<S, F, A, P> | SynchAction<S, F, P> | AdvancedSynchAction<S, F, A, P>;
/**
* S: The type of the state
* A: The type of the actions to pass to the children (used to check keys only).
*/
export type StateDecoratorActions<S, A extends DecoratedActions, P = {}> = {
[Prop in keyof A]: StateDecoratorAction<S, A[Prop], A, P>;
};
type OnPropsChangeReducer<S, P> = (s: S, newProps: P, updatedIndices: number[]) => S;
export type StateDecoratorOptions<S, A, P = {}> = {
/**
* The state decorator name. Use in debug traces to identify the useStateDecorator instance.
*/
name?: string;
/**
* Show logs in the console in development mode.
*/
logEnabled?: boolean;
/**
* List of action names that are marked as loading at initial time.
* As a render is done before first actions can be trigerred, some actions can be marked as loading at
* initial time.
*/
initialActionsMarkedLoading?: (keyof A)[];
/**
* Get a list of values that will be use as reference values.
* If they are different (shallow compare), onPropsChangeReducer then onPropsChange will be called.
*/
getPropsRefValues?: (props: P) => any[];
/**
* Triggered when values of reference from props have changed. Allow to call actions after a prop change.
*/
onPropsChange?: (s: S, newProps: any, actions: A, updatedIndices: number[]) => void;
/**
* Triggered when values of reference from props have changed. Allow to update state after a prop change.
* <b>null</b> means no change.
*/
onPropsChangeReducer?: OnPropsChangeReducer<S, P>;
/**
* The callback function called if an asynchronous function succeeded and a success messsage is defined.
*/
notifySuccess?: NotifyFunc;
/**
* The callback function called if an asynchronous function fails and an error messsage is defined.
*/
notifyError?: NotifyFunc;
/**
* The callback function injected in onDone and onFail of asynchronous functions to notify a semi success / failed request.
*/
notifyWarning?: NotifyFunc;
/**
* Initial actions. They are executed outside of a side effect to trigger asynchronous actions.
*/
onMount?: (actions: A, props: P, state: S) => void;
};
export {};