UNPKG

react-hooks-global-states

Version:

This is a package to easily handling global-state across your react-components using hooks.

369 lines (368 loc) 15.2 kB
export type AnyFunction = (...args: any[]) => any; /** * @description Represents a hook that returns a readonly state. */ export interface ReadonlyHook<State, StateMutator, Metadata extends BaseMetadata> extends ReadonlyStateApi<State, StateMutator, Metadata> { /** * @description Returns the current state value. */ (): State; /** * @description Returns a derived state based on the provided selector function. */ <Derivate>(selector: (state: State) => Derivate, dependencies?: unknown[]): Derivate; /** * @description Returns a derived state based on the provided selector function and configuration. */ <Derivate>(selector: (state: State) => Derivate, config?: UseHookOptions<Derivate, State>): Derivate; } /** * @description Hook to select a fragment of the state */ export interface SelectHook<State> { /** * @description Selects a fragment of the state using the provided selector function. * @param selector - The function to select a fragment of the state. * @param dependencies - Optional dependencies array to control re-evaluation. * @returns The selected fragment of the state. */ <Selection>(selector: (state: State) => Selection, dependencies?: unknown[]): Selection; /** * @description Selects a fragment of the state using the provided selector function and configuration. * @param selector - The function to select a fragment of the state. * @param args - Configuration options for the selection. * @returns The selected fragment of the state. */ <Selection>(selector: (state: State) => Selection, args?: UseHookOptions<Selection, State>): Selection; } /** * @description Represents the complete non-reactive API of a global state instance. * This API provides full control over the state, including reading, writing, subscribing, * and creating derived hooks or observable fragments. */ export type StateApi<State, StateMutator, Metadata extends BaseMetadata> = { /** * Returns the metadata * Metadata is additional non reactive information associated with the global state */ getMetadata: MetadataGetter<Metadata>; /** * Sets the metadata value * The metadata value is not reactive and wont trigger re-renders */ setMetadata: MetadataSetter<Metadata>; /** * @description Contains the generated action functions if custom actions are defined. * When actions exist, direct state mutation via `setState` is disabled and setState will be `null`. * If no actions are provided, this property is `null`. */ actions: StateMutator extends AnyFunction ? null : StateMutator; /** * @description Provides direct access to the state updater when no custom actions are defined. * When actions exist, direct state mutation via `setState` is disabled and this property will be `null`. * If no actions are provided, this property exposes the `setState` function. */ setState: StateMutator extends AnyFunction ? React.Dispatch<React.SetStateAction<State>> : null; /** * @description Get the current state value */ getState: () => State; /** * @description Subscribe to the state changes * You can subscribe to the whole state or to a fragment of the state by passing a selector as first parameter, * this can be used in non react environments to listen to the state changes */ subscribe: SubscribeToState<State>; /*** * @description Creates a new hooks that returns the result of the selector passed as a parameter * Your can create selector hooks of other selectors hooks and extract as many derived states as or fragments of the state as you want * The selector hook will be evaluated only if the result of the selector changes and the equality function returns false * you can customize the equality function by passing the isEqualRoot and isEqual parameters */ createSelectorHook: <Selection>(selector: (state: State) => Selection, args?: Omit<UseHookOptions<Selection, State>, 'dependencies'> & { name?: string; }) => ReadonlyHook<Selection, StateMutator, Metadata>; /** * @description Creates a function that allows you to subscribe to a fragment of the state * The observable selection will notify the subscribers only if the fragment changes and the equality function returns false * you can customize the equality function by passing the isEqualRoot and isEqual parameters */ createObservable: <Selection>(this: ReadonlyStateApi<State, StateMutator, Metadata>, selector: (state: State) => Selection, args?: { isEqual?: (current: Selection, next: Selection) => boolean; isEqualRoot?: (current: State, next: State) => boolean; /** * @description Name of the observable fragment for debugging purposes */ name?: string; }) => ObservableFragment<Selection, StateMutator, Metadata>; /** * @description Selects a fragment of the state using the provided selector function. */ select: SelectHook<State>; /** * @description Sugared hook to use the global state in React components * Allows you to tread the global hook as an store, with better semantics * * @example * ```tsx * const contacts = createContext<ContactType[]>([]); * * function ContactsList() { * const [contacts, setContacts] = contacts.use(); * ``` * * This is more practical since the StateApi is slightly more complex than a simple hook * * @example * Using a global state: * * ```tsx * const [state, setState, metadata] = state.use(); * * const selection = state.select((state) => state.someFragment); * * const unsubscribe = state.subscribe((state) => { ... }); * * const useFragment = state.createSelectorHook((state) => state.someFragment); * * const observable = state.createObservable((state) => state.someFragment); * ``` */ use: StateHook<State, StateMutator, Metadata>; /** * @description Disposes the global state instance, cleaning up all resources and subscriptions. */ dispose: () => void; /** * @deprecated * @description Useful for debugging purposes, exposes the current subscribers of the store * You'll probably not need to use this in your application */ subscribers: Set<SubscriberParameters>; }; /** * @description Readonly version of the StateApi, excluding mutative methods. */ export type ReadonlyStateApi<State, StateMutator, Metadata extends BaseMetadata> = Pick<StateApi<State, StateMutator, Metadata>, 'dispose' | 'getState' | 'subscribe' | 'createSelectorHook' | 'createObservable' | 'subscribers'>; /** * @description Function that allows you to subscribe to a fragment of the state */ export type ObservableFragment<State, StateMutator, Metadata extends BaseMetadata> = SubscribeToState<State> & Pick<StateApi<State, StateMutator, Metadata>, 'getState' | 'subscribe' | 'createSelectorHook' | 'createObservable' | 'dispose' | 'subscribers'>; export interface StateHook<State, StateMutator, Metadata extends BaseMetadata> extends StateApi<State, StateMutator, Metadata> { /** * @description React hook that provides access to the state, state mutator, and metadata. */ (): Readonly<[state: State, stateMutator: StateMutator, metadata: Metadata]>; /** * @description React hook that provides a derived state based on the provided selector function. */ <Derivate>(selector: (state: State) => Derivate, dependencies?: unknown[]): Readonly<[state: Derivate, stateMutator: StateMutator, metadata: Metadata]>; /** * @description React hook that provides a derived state based on the provided selector function and configuration. */ <Derivate>(selector: (state: State) => Derivate, config?: UseHookOptions<Derivate, State>): Readonly<[state: Derivate, stateMutator: StateMutator, metadata: Metadata]>; } /** * @description Function to set the metadata value * The metadata value is not reactive and wont trigger re-renders */ export type MetadataSetter<Metadata extends BaseMetadata> = (setter: Metadata | ((metadata: Metadata) => Metadata)) => void; /** * @description Represents the changes in the state */ export type StateChanges<State> = { state: State; previousState: State | undefined; identifier: string | undefined; }; /** * API for the actions of the global states **/ export type StoreTools<State, StateMutator = React.Dispatch<React.SetStateAction<State>>, Metadata extends BaseMetadata = BaseMetadata> = { /** * The actions available for the global state if provided */ actions: StateMutator extends AnyFunction ? null : StateMutator; /** * @description Metadata associated with the global state */ getMetadata: () => Metadata; /** * @description Current state value */ getState: () => State; /** * @description Sets the metadata value */ setMetadata: MetadataSetter<Metadata>; /** * @description Function to set the state value */ setState: (setter: React.SetStateAction<State>, args?: { /** * @description Force update even if the state value did not change, this is for advanced use cases only */ forceUpdate?: boolean; /** * @description Optional identifier visible on the devtools */ identifier?: string; }) => void; /** * @description Subscribe to the state changes * You can subscribe to the whole state or to a fragment of the state by passing a selector as first parameter, * this can be used in non react environments to listen to the state changes * * @example * ```ts * const unsubscribe = storeTools.subscribe((state) => { * console.log('State changed:', state); * }); * * // To unsubscribe later * unsubscribe(); * ``` */ subscribe: SubscribeToState<State>; }; /** * contract for the storeActionsConfig configuration */ export interface ActionCollectionConfig<State, Metadata extends BaseMetadata, ThisAPI = Record<string, (...parameters: any[]) => unknown>> { readonly [key: string]: { (this: ThisAPI, ...parameters: any[]): (this: ThisAPI, storeTools: StoreTools<State, Record<string, (...parameters: any[]) => unknown | void>, Metadata>) => unknown | void; }; } /** * @description Resulting type of the action collection configuration */ export type ActionCollectionResult<State, Metadata extends BaseMetadata, ActionsConfig extends ActionCollectionConfig<State, Metadata>> = { [key in keyof ActionsConfig]: { (...params: Parameters<ActionsConfig[key]>): ReturnType<ReturnType<ActionsConfig[key]>>; }; }; /** * Callbacks for the global store lifecycle events */ export type GlobalStoreCallbacks<State, StateMutator, Metadata extends BaseMetadata> = { /** * @description Called when the store is initialized */ onInit?: (args: StoreTools<State, StateMutator, Metadata>) => void; /** * @description Called when the state has changed */ onStateChanged?: (args: StoreTools<State, StateMutator, Metadata> & StateChanges<State>) => void; /** * @description Called when a new subscription is created */ onSubscribed?: (args: StoreTools<State, StateMutator, Metadata>, subscription: SubscriberParameters) => void; /** * @description Called to determine whether to prevent a state change */ computePreventStateChange?: (args: StoreTools<State, StateMutator, Metadata> & StateChanges<State>) => boolean; /** * @description Called when the store is unmounted, only applicable in context stores */ onUnMount?: (store: StoreTools<State, StateMutator, Metadata>) => void; }; /** * @description Configuration options for the use hook */ export type UseHookOptions<State, TRoot = any> = { isEqual?: (current: State, next: State) => boolean; isEqualRoot?: (current: TRoot, next: TRoot) => boolean; dependencies?: unknown[]; }; /** * @description Callback function to unsubscribe from the store changes */ export type UnsubscribeCallback = () => void; /** * @description Configuration for the subscribe callback */ export type SubscribeCallbackConfig<State> = UseHookOptions<State> & { /** * By default the callback is executed immediately after the subscription */ skipFirst?: boolean; }; /** * Callback function to subscribe to the store changes */ export type SubscribeCallback<State> = (state: State) => void; /** * @description Subscribe to the state changes * You can subscribe to the whole state or to a fragment of the state by passing a selector as first parameter * This can be used in non react environments to listen to the state changes */ export type SubscribeToState<State> = { /** * @description Subscribe to the whole state changes * * @example * ```ts * const unsubscribe = store.subscribe((state) => { * console.log('State changed:', state); * }); * * // To unsubscribe later * unsubscribe(); * ``` */ (subscription: SubscribeCallback<State>, config?: SubscribeCallbackConfig<State>): UnsubscribeCallback; /** * @description Subscribe to a fragment of the state changes * * @example * ```ts * const unsubscribe = store.subscribe( * (fragment) => { * console.log('Fragment changed:', fragment); * }, * (state) => { * console.log('Selected fragment changed:', state.someFragment); * } * ); * * // To unsubscribe later * unsubscribe(); * ``` */ <TDerivate>(selector: SelectorCallback<State, TDerivate>, subscription: SubscribeCallback<TDerivate>, config?: SubscribeCallbackConfig<TDerivate>): UnsubscribeCallback; }; /** * @description Metadata, non reactive additional information associated with the global state */ export type BaseMetadata = Record<string, unknown>; /** * @description Function to get the metadata */ export type MetadataGetter<Metadata extends BaseMetadata> = () => Metadata; /** * @description Selector function to derive a fragment of the state */ export type SelectorCallback<State, TDerivate> = (state: State) => TDerivate; /** * @description Parameters for the store subscription */ export type SubscriberParameters = { selector: SelectorCallback<any, any> | undefined; currentState: unknown; /** * @description notification callback */ onStoreChange: SubscriptionCallback | (() => void); } & UseHookOptions<any> & SubscribeCallbackConfig<any>; /** * @description * This is the final listener of the store changes, it can be a subscription or a setState * @param {unknown} params - The parameters of the subscription * @param {unknown} params.state - The new state * @param {string} params.identifier - Optional identifier for the setState call */ export type SubscriptionCallback<State = unknown> = (params: { state: State; }, args: { identifier?: string; }) => void;