@phnq/state
Version:
State management for React
188 lines (187 loc) • 8.33 kB
TypeScript
import { ComponentType } from 'react';
declare type GenericObject = Record<string, any>;
declare type State<S> = {
[K in keyof S]: S[K] | ((state: Omit<S, K>) => S[K]);
} & {
[key: string]: unknown;
};
interface ImplicitActions<S, A> {
/**
* This gets called when the state provider is mounted, or if the `ressetState(true)`
* action is called from a consumer.
*/
init?(): void;
/**
* This gets called when the state provider is unmounted.
*/
destroy?(): void;
/**
* If specifified, errors thrown from actions will result in this function being
* called.
* @param error the error that was thrown in the action.
* @param action the name of the action where the error was thrown.
*/
onError?(error: unknown, action: keyof Actions<S, A>): void;
/**
* This gets called after state changes have been made.
* @param changedKeys the state keys whose values changed.
* @param changeInfo information about the change.
*/
onChange?(changedKeys: (keyof S)[], changeInfo: StateChangeInfo<S>): void;
}
interface StateChangeInfo<S> {
/**
* The previous state -- i.e. before the state changes.
*/
prevState: S;
/**
* Identifier of the change initiator. This is typically undefined which indicates an internal state change.
*/
source?: string;
/**
* Whether the state change was triggered externally.
*/
viaExternal: boolean;
}
declare type Actions<S, A> = A & ImplicitActions<S, A>;
declare type ActionFunction = (...args: never[]) => void | Promise<void>;
export declare type VoidActions<T> = Record<keyof T, ActionFunction>;
interface GetActionsParams<S, E> {
/**
* Returns a copy of the current state.
* @returns a copy of the current state
*/
getState(): S;
/**
* Returns a copy of the named external state.
* @returns a copy of the named external state
*/
getState<K extends keyof E>(extStateName: K): E[K];
/**
* Used to set some someset of the full state.
* @param subState a subset of the full state
*/
setState(subState: Partial<S>): void;
/**
* Resets the full state to its initial value.
* @param reinitialize whether or not the `init()` lifecycle method should be called.
*/
resetState(reinitialize?: boolean): void;
}
interface ChangeListener<S> {
id: number;
order: number;
onChangeInternal(info: {
changedKeys: (keyof S)[];
stateChanges: Partial<S>;
newState: StateBroker<S>['state'];
version: number;
}): void;
}
interface StateBroker<S = unknown, A = unknown> {
found: boolean;
version: number;
state: S;
setState(stateChanges: Partial<S>, options?: SetStateOptions): void;
actions: Readonly<Actions<S, A>>;
addListener(listener: ChangeListener<S>): void;
removeListener(id: number): void;
}
interface SetStateOptions {
incremental?: boolean;
source?: string;
}
export interface StateFactory<S = unknown, A = unknown, PR extends keyof S = never> {
/**
* This HOC creates a state provider by wrapping a component. The returned component's
* interface will be identical to the wrapped component's interface. Descendents of this
* state provider in the component hierarchy will be able to interact with its state via
* `useState()` hook or the (deprecated) `consumer` HOC.
* @param Wrapped the component to be wrapped.
*/
provider: <T extends GenericObject>(Wrapped: ComponentType<T>) => ComponentType<T>;
/**
* This hook returns the complete state and actions of its nearest counterpart provider
* ancestor in the component hierarchy. Referencing an attribute in the returned state
* implicitly subscribes the current render context to be notified when that attribute's
* value changes. The "notification" is a render. For example, consider:
* ```ts
* const { firstName } = userState.useState();
* ```
* A render will be triggered if `firstName` changes in the state. However, suppose there
* is also a value in the state for the attribute `lastName`; no render will occur as a
* result of `lastName` changing.
* @param alwaysRender if true, the component will re-render whenever any state changes. Use
* this as a last resort since it will have a negative performance impact.
*/
useState(alwaysRender?: boolean): Omit<S, PR> & Readonly<A>;
/**
* This hook provides a low-level way of interacting with the state. State changes are conveyed
* via the supplied `onStateChange` callback. The return value is a function that can be used
* to affect state changes directly, bypassing the action functions. This direct access to the
* state breaks the typical encapsulation conventions/protections afforded by action functions,
* so use of this hook is discouraged in all but the most exceptional of circumstances. As the
* name implies, this hook is intended to be used for generic synchronization tasks.
* @param onStateChange a callback that will be called whenever the state changes.
* @returns a function that can be used to affect state changes directly.
*/
useSync(onStateChange: (info: {
state: S;
changes: Partial<S>;
}) => void): StateBroker<S, A>['setState'];
/**
* @deprecated
* Wrapping a component with this HOC adds the ancestor provider's state attribute keys
* to the wrapped component's props.
* Note: this HOC is deprecated in favour of the more flexible and unobtrusive `useState()`.
* @param Wrapped the component to be wrapped.
*/
consumer<T = unknown>(Wrapped: ComponentType<T>): ComponentType<Omit<T, keyof (Omit<S, PR> & A)>>;
/**
* @deprecated
* Similar to `comsumer`, this HOC also wraps a component to provide access to its ancestor
* provider's state, but it additionally allows you to map the state to an aribitrary list
* of props.
* @param mapFn
*/
map<T = unknown>(mapFn: (s: State<Omit<S, PR>> & Actions<Omit<S, PR>, A>) => T): (Wrapped: ComponentType<any>) => ComponentType<any>;
}
interface Options<S, E = unknown, P = unknown> {
/**
* Named imported state factories.
*/
imported?: Record<keyof E, StateFactory>;
/**
* function for adding behaviour to the provider in the form of a HOC.
*/
mapProvider?: MapProvider<P>;
deepCompare?: (keyof S)[];
}
export declare type GetActions<S, A, P extends GenericObject = GenericObject, E extends GenericObject = GenericObject> = (getActionsParams: GetActionsParams<S, E> & P) => Actions<S, A>;
declare type MapProvider<P> = <T = unknown>(p: ComponentType<P>) => ComponentType<T & Omit<T, keyof P>>;
/**
* Creates a factory for generating state providers, consumer hooks and HOCs. A single state
* factory can generate multiple providers/consumers.
* @param name The name of the state. Only used for logging.
* @param defaultState The initial value of the state, and derived state functions.
* @param getActions Function that returns the action functions.
* @returns a state factory.
*/
export declare function createState<S extends object, A extends VoidActions<A>, PR extends keyof S = never>(name: string, defaultState: State<S>, getActions: GetActions<S, A, GenericObject, GenericObject>): StateFactory<S, A, PR>;
/**
* Creates a factory for generating state providers, consumer hooks and HOCs. A single state
* factory can generate multiple providers/consumers.
* @param name The name of the state. Only used for logging.
* @param defaultState The initial value of the state, and derived state functions.
* @param options Options for the state.
* @param getActions Function that returns the action functions.
* @returns a state factory.
*/
export declare function createState<S extends object, A extends VoidActions<A>, P extends GenericObject = GenericObject, E extends GenericObject = GenericObject, PR extends keyof S = never>(name: string, options: Options<S, E, P>, defaultState: State<S>, getActions: GetActions<S, A, P, E>): StateFactory<S, A, PR>;
declare global {
interface Window {
getAllStates: () => [string, unknown][];
logAllStates: () => void;
}
}
export {};