react-hooks-global-states
Version:
A package to easily handle global state across your React components using hooks.
1,220 lines • 50.8 kB
TypeScript
import type GlobalStore from './GlobalStore';
import type { Context as ReactContext, PropsWithChildren } from 'react';
export type Any = any;
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.
* If no actions are provided, this property is `null`.
*/
actions: StateMutator extends AnyFunction ? null : StateMutator;
/**
* @description Provides direct access to the state updater.
* Always available for testing purposes, even when actions are defined.
* In production, prefer using actions when they are defined.
*/
setState: React.Dispatch<React.SetStateAction<State>>;
/**
* @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;
/**
* PLEASE USE CALLBACK-BASED INITIALIZERS FOR STATE AND METADATA IF YOU PLAN TO USE RESET OFTEN.
*
* @description Resets the store to its initial state and metadata as provided during creation.
* This method is reserved for advanced use cases and testing scenarios, use with caution.
* If the initial state and metadata are static values this may not work as you expect.
*/
reset(): void;
/**
* @description Resets the store to a new initial state and re-runs initialization (including onInit callbacks).
* Existing subscribers are maintained and notified of the new state.
* Useful for testing scenarios where you need to reinitialize the store.
* @param newState - The new initial state to reset to
* @param newMetadata - The new metadata to set after reset
*/
reset(newState: State, newMetadata: Metadata): 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]>>;
};
};
export type CleanupFunction = () => void;
/**
* 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 | Promise<void> | CleanupFunction;
/**
* @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;
export type GlobalStoreContextCallbacks<State, StateMutator, Metadata extends BaseMetadata> = {
/**
* @description Optional callback invoked after the context is created,
*/
onCreated?: (
/**
* @description Full context instance
*/
storeTools: ContextStoreTools<State, StateMutator extends AnyFunction ? null : StateMutator, Metadata>,
/**
* @description Underlying store instance
*/
store: GlobalStore<State, Metadata, unknown, Any>) => void;
/**
* @description Called when the context provider is mounted
*/
onMounted?: (
/**
* @description Full context instance
*/
storeTools: ContextStoreTools<State, StateMutator extends AnyFunction ? null : StateMutator, Metadata>,
/**
* @description Underlying store instance
*/
store: GlobalStore<State, Metadata, unknown, Any>) => void | UnsubscribeCallback;
};
export type ContextStoreCallbacks<State, StateMutator, Metadata extends BaseMetadata> = GlobalStoreCallbacks<State, StateMutator, Metadata> & GlobalStoreContextCallbacks<State, StateMutator, Metadata>;
/**
* @description Resulting type of the action collection configuration
*/
export type ContextActionCollectionResult<State, Metadata extends BaseMetadata, ActionsConfig extends ContextActionCollectionConfig<State, Metadata>> = {
[key in keyof ActionsConfig]: {
(...params: Parameters<ActionsConfig[key]>): ReturnType<ReturnType<ActionsConfig[key]>>;
};
};
/**
* contract for the storeActionsConfig configuration
*/
export interface ContextActionCollectionConfig<State, Metadata extends BaseMetadata, ThisAPI = Record<string, (...parameters: Any[]) => unknown>> {
readonly [key: string]: {
(this: ThisAPI, ...parameters: Any[]): (this: ThisAPI, storeTools: ContextStoreTools<State, Record<string, (...parameters: Any[]) => unknown | void>, Metadata>) => unknown | void;
};
}
/**
* @description Extensions methods for the context store tools
*/
export type ContextStoreToolsExtensions<State, StateMutator, Metadata extends BaseMetadata> = {
/**
* @description Main hook of the context
*
* @example
* ```tsx
* type CounterContext = import('../../stores/counter').CounterContext;
*
* const useLogCount = () => {
* return ({ use }: CounterContext) => {
* const count = use.select(s => s.count);
*
* console.log('Count changed:', count);
* };
* }
*
* // Usage in store
* import useLogCount from './actions/useLogCount';
*
* const counter = createContext({ count: 0 }, {
* actions: {
* useLogCount,
* }
* });
*
* // Usage in component
* import counter from '../stores/counter';
*
* const CounterLogger = () => {
* // access to the actions is NOT-REACTIVE
* const { useLogCount } = counter.use.actions();
*
* useLogCount();
* }
* ```
*/
use: ContextHook<State, StateMutator, Metadata>;
};
/**
* @description Store tools specialized for context usage
*/
export type ContextStoreTools<State, StateMutator, Metadata extends BaseMetadata> = StoreTools<State, StateMutator, Metadata> & ContextStoreToolsExtensions<State, StateMutator, Metadata>;
/**
* @description Extensions methods for the ContextProvider component
*/
export type ContextProviderExtensions<State, StateMutator, Metadata extends BaseMetadata> = {
/**
* Creates a provider wrapper which allows to capture the context value,
* useful for testing purposes.
* @param options configuration options for the provider wrapper
* @param options.value optional initial state or initializer function
* @param options.onCreated optional callback invoked after the context is created
* @returns an object containing the wrapper component and a reference to the context value
*/
makeProviderWrapper: (options?: {
/**
* @description Optional initial state or initializer function, useful for testing, storybooks, etc.
*/
value?: State | ((initialValue: State) => State);
} & GlobalStoreContextCallbacks<State, StateMutator extends AnyFunction ? null : StateMutator, Metadata>) => {
/**
* Provider for the context
*/
wrapper: React.FC<PropsWithChildren<{
/**
* @description Optional initial state or initializer function, useful for testing, storybooks, etc.
*/
value?: State | ((initialValue: State) => State);
}>>;
/**
* Reference to the current context value
*/
context: {
/**
* @description Current context value
*/
current: ContextStoreTools<State, StateMutator extends AnyFunction ? null : StateMutator, Metadata>;
/**
* @description Underlying store instance
*/
instance: GlobalStore<State, Metadata, unknown, any>;
};
};
};
/**
* @description Creates a React context provider component for the given global state.
* @param value Optional initial state or initializer function, useful for testing.
* @param onCreated Optional callback invoked after the context is created, receiving the full context instance.
*/
export type ContextProvider<State, StateMutator, Metadata extends BaseMetadata> = React.FC<PropsWithChildren<{
/**
* @description Optional initial state or initializer function, useful for testing, storybooks, etc.
*/
value?: State | ((initialValue: State) => State);
} & GlobalStoreContextCallbacks<State, StateMutator extends AnyFunction ? null : StateMutator, Metadata>>> & ContextProviderExtensions<State, StateMutator, Metadata>;
/**
* @description Represents a hook that returns a readonly state.
*/
export interface ReadonlyContextHook<State, StateMutator, Metadata extends BaseMetadata> extends ReadonlyContextPublicApi<State, StateMutator, Metadata> {
/**
* @description Returns the full state.
*/
(): State;
/**
* @description Returns a derived value from the state using the provided selector function.
* @param selector A function that selects a part of the state.
* @param dependencies Optional array of dependencies to control when the selector is re-evaluated.
* @returns The derived value from the state.
*/
<Derivate>(selector: (state: State) => Derivate, dependencies?: unknown[]): Derivate;
/**
* @description Returns a derived value from the state using the provided selector function.
* @param selector A function that selects a part of the state.
* @param options Optional configuration for the selector hook.
* @param options.isEqual Optional equality function to compare the selected fragment.
* @param options.isEqualRoot Optional equality function to compare the root state.
* @param options.dependencies Optional array of dependencies to control when the selector is re-evaluated.
* @returns The derived value from the state.
*/
<Derivate>(selector: (state: State) => Derivate, options?: UseHookOptions<Derivate, State>): Derivate;
}
/**
* @description Hook for accessing a context's state, mutator (setState or actions), and metadata.
* @returns A read-only tuple containing:
* - state: the current state, or the derived value when a selector is used
* - stateMutator: a function or actions collection to update the state
* - metadata: the current context metadata
*
* @example
* ```tsx
* // Simple usage (full state)
* const [state, setState] = useTodosContext();
*
* // With a selector (preferred for render isolation)
* const [todos, actions] = useTodosContext(s => s.todos);
*
* actions.setTodos(next);
* ```
*/
export interface ContextHook<State, StateMutator, Metadata extends BaseMetadata> extends ContextPublicApi<State, StateMutator, Metadata> {
/**
* @description Retrieves the full state, state mutator (setState or actions), and metadata.
*/
(): Readonly<[state: State, stateMutator: StateMutator, metadata: Metadata]>;
/**
* @description Retrieves a derived value from the state using the provided selector function.
* @param selector A function that selects a part of the state.
* @param dependencies Optional array of dependencies to control when the selector is re-evaluated.
* @returns A read-only tuple containing the derived state, state mutator (setState or actions), and metadata.
*/
<Derivate>(selector: (state: State) => Derivate, dependencies?: unknown[]): Readonly<[state: Derivate, stateMutator: StateMutator, metadata: Metadata]>;
/**
* @description Retrieves a derived value from the state using the provided selector function.
* @param selector A function that selects a part of the state.
* @param dependencies Optional array of dependencies to control when the selector is re-evaluated.
* @returns A read-only tuple containing the derived state, state mutator (setState or actions), and metadata.
*/
<Derivate>(selector: (state: State) => Derivate, config?: UseHookOptions<Derivate, State>): Readonly<[state: Derivate, stateMutator: StateMutator, metadata: Metadata]>;
}
export type ContextPublicApi<State, StateMutator, Metadata extends BaseMetadata> = {
/**
* @description [NOT A HOOK, NON-REACTIVE]
* Creates a derived hook that subscribes to a selected fragment of the context state.
* The selector determines which portion of the state the new hook exposes.
* This hook must be used within the corresponding context provider.
*
* @param selector A function that selects a part of the state.
* @param args Optional configuration for the derived hook, including:
* - isEqual: A function to compare the current and next selected fragment for equality.
* - isEqualRoot: A function to compare the entire state for equality.
* - name: An optional name for debugging purposes.
* @returns A new context hook that provides access to the selected fragment of the state,
* along with the state mutator and metadata.
*
* @example
* ```tsx
* const useTodos = createContext({
* todos: [],
* filter: '',
* }, {
* actions: {
* setFilter(filter: string) {
* ...
* });
*
* const useFilter = useTodos.createSelectorHook((state) => {
* return state.filter;
* });
*
* function FilterComponent() {
* // The selector only listen to the selected fragment (filter)
* // But has access to the full actions collection
* const [filter, { setFilter }] = useFilter();
*
* return (
* <input
* value={filter}
* onChange={(e) => setFilter(e.target.value)}
* />
* );
* }
* ```
*/
createSelectorHook: <Derivate>(this: ReadonlyContextPublicApi<State, StateMutator, Metadata>, selector: (state: State) => Derivate, args?: Omit<UseHookOptions<Derivate, State>, 'dependencies'> & {
name?: string;
}) => ReadonlyContextHook<Derivate, StateMutator, Metadata>;
/**
* @description [NON-REACTIVE]
* Hook that provides non-reactive access to the context API.
* This allows direct interaction with the context’s state, metadata, and actions
* without triggering component re-renders.
* @returns An object containing the context API methods and properties.
*/
api: () => StateApi<State, StateMutator, Metadata>;
/**
* @description [NON-REACTIVE]
* Provides direct access to the context's actions, if available.
*/
actions: () => StateMutator extends AnyFunction ? null : StateMutator;
/**
* @description Selects a fragment of the state using the provided selector function.
*/
select: SelectHook<State>;
/**
* @description
* Creates a hook 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
*
* @example
* ```tsx
* const observable = store.use.observable(state => state.count);
*
* useEffect(() => {
* const unsubscribe = observable.subscribe(() => {
* // do something when the selected fragment changes
* });
*
* return () => {
* unsubscribe();
* };
* }, [observable]);
* ```
*/
observable: <Selection>(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 display name for debugging purposes
*/
readonly displayName: string;
};
/**
* @description Readonly version of the ContextPublicApi, expose by selectors and observables
*/
export type ReadonlyContextPublicApi<State, StateMutator, Metadata extends BaseMetadata> = Pick<ContextPublicApi<State, StateMutator, Metadata>, 'createSelectorHook' | 'displayName'> & {
/**
* @description Hook that provides non-reactive access to the context API.
* This allows direct interaction with the context’s state, metadata, and actions
* without triggering component re-renders.
* @returns An object containing the context API methods and properties.
*/
api: () => ReadonlyStateApi<State, StateMutator, Metadata>;
};
export interface CreateContext {
/**
* @description Creates a highly granular React context with its associated provider and state hook.
* @param value Initial state value or initializer function.
* @returns An object containing:
* - **`use`** — A custom hook to read and mutate the context state.
* Supports selectors for granular subscriptions and returns `[state, stateMutator, metadata]`.
* - **`Provider`** — A React component that provides the context to its descendants.
* It accepts an optional initial value and `onCreated` callback.
* - **`Context`** — The raw React `Context` object for advanced usage, such as integration with
* external tools or non-React consumers.
*/
<State, StateMutator = React.Dispatch<React.SetStateAction<State>>>(value: State | (() => State)): {
/**
* @description Hook and API for interacting with the context.
* This hook provides access to the context state, actions, and metadata.
*
* There are two ways to use the hook
* @example
* The more simple and familiar way is to use it as a regular hook
*
* ```tsx
* const { Context, Provider, user: useUser} = createContext({ name: 'John', age: 30 });
*
* function UserProfile() {
* const [state, setState, metadata] = useUser();
*
* ....
* }
* ```
*
* @example
* The recommended, more sematic and easier to read way:
*
* ```tsx
* const user = createContext({ name: 'John', age: 30 });
*
* <user.Provider>
* <UserProfile />
* </user.Provider>
*
* function UserProfile() {
* const [state, setState, metadata] = user.use();
* const userContext = user.use.api();
* const userName = user.use.select(s => s.name);
* // ...
* }
*/
use: ContextHook<State, StateMutator, BaseMetadata>;
/**
* @description Provider for the context
*/
Provider: ContextProvider<State, StateMutator, BaseMetadata>;
/**
* @description The raw React Context object
*/
Context: ReactContext<ContextHook<State, StateMutator, BaseMetadata> | null>;
};
/**
* @description Creates a highly granular React context with its associated provider and state hook.
* @param value Initial state value or initializer function.
* @param args Additional configuration for the context.
* @param args.name Optional name for debugging purposes.
* @param args.metadata Optional non-reactive metadata associated with the state.
* @param args.callbacks Optional lifecycle callbacks for the context.
* @param args.actions Optional actions to restrict state mutations [if provided `setState` will be nullified].
* @returns An object containing:
* - **`use`** — A custom hook to read and mutate the context state.
* Supports selectors for granular subscriptions and returns `[state, stateMutator, metadata]`.
* - **`Provider`** — A React component that provides the context to its descendants.
* It accepts an optional initial value and `onCreated` callback.
* - **`Context`** — The raw React `Context` object for advanced usage, such as integration with
* external tools or non-React consumers.
*/
<State, Metadata extends BaseMetadata, ActionsConfig extends ContextActionCollectionConfig<State, Metadata> | null | {}, StateMutator = keyof ActionsConfig extends never | undefined ? React.Dispatch<React.SetStateAction<State>> : ContextActionCollectionResult<State, Metadata, NonNullable<ActionsConfig>>>(
/**
* @description Initial state value or initializer function.
*/
value: State | (() => State),
/**
* @description Additional configuration for the context.
* @param args.name Optional name for debugging purposes.
* @param args.metadata Optional non-reactive metadata associated with the state.
* @param args.callbacks Optional lifecycle callbacks for the context.
* @param args.actions Optional actions to restrict state mutations [if provided `setState` will be nullified].
*/
args: {
name?: string;
metadata?: Metadata | (() => Metadata);
callbacks?: ContextStoreCallbacks<Any, AnyActions, Any>;
actions?: ActionsConfig;
}): {
/**
* @description Hook and API for interacting with the context.
* This hook provides access to the context state, actions, and metadata.
*
* There are two ways to use the hook
* @example
* The more simple and familiar way is to use it as a regular hook
*
* ```tsx
* const { Context, Provider, user: useUser} = createContext({ name: 'John', age: 30 });
*
* function UserProfile() {
* const [state, setState, metadata] = useUser();
*
* ....
* }
* ```
*
* @example
* The recommended, more sematic and easier to read way:
*
* ```tsx
* const user = createContext({ name: 'John', age: 30 });
*
* <user.Provider>
* <UserProfile />
* </user.Provider>
*
* function UserProfile() {
* const [state, setState, metadata] = user.use();
* const userContext = user.use.api();
* const userName = user.use.select(s => s.name);
* // ...
* }
*/
use: ContextHook<State, StateMutator, Metadata>;
/**
* @description Provider for the context
*/
Provider: ContextProvider<State, StateMutator, Metadata>;
/**
* @description Raw React Context object
*/
Context: ReactContext<ContextHook<State, StateMutator, Metadata> | null>;
};
/**
* @description Creates a highly granular React context with its associated provider and state hook.
* @param value Initial state value or initializer function.
* @param args Additional configuration for the context.
* @param args.name Optional name for debugging purposes.
* @param args.metadata Optional non-reactive metadata associated with the state.
* @param args.callbacks Optional lifecycle callbacks for the context.
* @param args.actions Optional actions to restrict state mutations [if provided `setState` will be nullified].
* @returns An object containing:
* - **`use`** — A custom hook to read and mutate the context state.
* Supports selectors for granular subscriptions and returns `[state, stateMutator, metadata]`.
* - **`Provider`** — A React component that provides the context to its descendants.
* It accepts an optional initial value and `onCreated` callback.
* - **`Context`** — The raw React `Context` object for advanced usage, such as integration with
* external tools or non-React consumers.
*/
<State, Metadata extends BaseMetadata, ActionsConfig extends ContextActionCollectionConfig<State, Metadata>>(
/**
* @description Initial state value or initializer function.
*/
value: State | (() => State),
/**
* @description Additional configuration for the context.
* @param args.name Optional name for debugging purposes.
* @param args.metadata Optional non-reactive metadata associated with the state.
* @param args.callbacks Optional lifecycle callbacks for the context.
* @param args.actions Optional actions to restrict state mutations [if provided `setState` will be nullified].
*/
args: {
name?: string;
metadata?: Metadata | (() => Metadata);
callbacks?: ContextStoreCallbacks<Any, AnyActions, Any>;
actions: ActionsConfig;
}): {
/**
* @description Hook and API for interacting with the context.
* This hook provides access to the context state, actions, and metadata.
*
* There are two ways to use the hook
* @example
* The more simple and familiar way is to use it as a regular hook
*
* ```tsx
* const { Context, Provider, user: useUser} = createContext({ name: 'John', age: 30 });
*
* function UserProfile() {
* const [state, setState, metadata] = useUser();
*
* ....
* }
* ```
*
* @example
* The recommended, more sematic and easier to read way:
*
* ```tsx
* const user = createContext({ name: 'John', age: 30 });
*
* <user.Provider>
* <UserProfile />
* </user.Provider>
*
* function UserProfile() {
* const [state, setState, metadata] = user.use();
* const userContext = user.use.api();
* const userName = user.use.select(s => s.name);
* // ...
* }
*/
use: ContextHook<State, ContextActionCollectionResult<State, Metadata, ActionsConfig>, Metadata>;
/**
* @description Provider for the context
*/
Provider: ContextProvider<State, ContextActionCollectionResult<State, Metadata, ActionsConfig>, Metadata>;
/**
* @description Raw React Context object
*/
Context: ReactContext<ContextHook<State, ContextActionCollectionResult<State, Metadata, ActionsConfig>, Metadata> | null>;
};
}
/**
* @description Infers the context API type
*
* @example
* ```ts
* const counter = createContext(0);
*
* type CounterContextApi = InferContextApi<typeof counter.Context>;
*
* // Equivalent to:
* ContextApi<number, React.Dispatch<React.SetStateAction<number>>, BaseMetadata>;
* ```
*/
export type InferContextApi<Context extends ReactContext<ContextHook<Any, Any, Any> | null>> = NonNullable<React.ContextType<Context>> extends ContextHook<infer State, infer StateMutator, infer Metadata> ? ContextStoreTools<State, StateMutator extends AnyFunction ? null : StateMutator, Metadata> : never;
/**
* Typescript is unable to infer the actions type correctly for the lifecycle callbacks
* so we use a generic AnyActions type here to bypass that limitation.
*
* The parameter could still be typed before using it with
* ```ts
* type StoreTools = InferStateApi<typeof <hook>>;
*
* onInit: (tools) => {
* const storeTools = tools as StoreTools;
* // ...
* }
*
* or when dealing with context:
*
* type ContextApi = InferContextApi<typeof <context>>;
*
* onInit: (tools) => {
* const storeTools = tools as ContextApi;
* // ...
* }
* ```
*/
export type AnyActions = Record<string, (...args: Any[]) => Any>;
export interface CreateGlobalState {
/**
* Creates a global state hook.
* @param state initial state value or a callback function that returns the initial state
* @returns a state hook for your components
*
* @example
* ```tsx
* const useCounter = createGlobalState(0);
*
* function Counter() {
* const [count, setCount] = useCounter();
* return (
* <div>
* <p>Count: {count}</p>
* <button onClick={() =>
* setCount(prev => prev + 1)
* }>Increment</button>
* </div>
* );
* }
* ```
*
* @example Using a callback to initialize state
* ```tsx
* const useCounter = createGlobalState(() => {
* // Expensive computation or conditional logic
* return localStorage.getItem('count') ? parseInt(localStorage.getItem('count')) : 0;
* });
* ```
*
* @example You can also use a more semantic and declarative approach
* ```tsx
* const counter = createGlobalState(0);
*
* function Counter() {
* const [count, setCount] = counter.use();
* const count = counter.use.select();
*
* counter.setState(prev => prev + 1);
*
* // if you have actions
* counter.actions.someAction();
* ```
*/
<State>(state: State | (() => State)): StateHook<State, React.Dispatch<React.SetStateAction<State>>, BaseMetadata>;
/**
* Creates a global state hook that you can use across your application
* @param state initial state value or a callback function that returns the initial state
* @param args additional configuration for the global state
* @param args.name optional name for debugging purposes
* @param args.metadata optional non-reactive metadata associated with the state (can be a value or callback)
* @param args.callbacks optional lifecycle callbacks for the global state
* @param args.actions optional actions to restrict state mutations [if provided `setState` will be nullified]
* @returns a state hook that you can use in your components
*
* @example
* ```tsx
* const useCounter = createGlobalState(0, {
* actions: {
* increase() {
* return ({ setState }) => {
* setState((c) => c + 1);
* };
* },
* decrease(amount: number) {
* return ({ setState }) => {
* setState((c) => c - amount);
* };
* },
* },
* });
*
* function Counter() {
* const [count, {
* increase,
* decrease
* }] = useCounter();
*
* return (
* <div>
* <p>Count: {count}</p>
* <button onClick={increase}>
* Increment
* </button>
* <button onClick={() => {
* decrease(1);
* }}>
* Decrement
* </button>
* </div>
* );
* }
* ```
*
* @example Using callbacks for state and metadata initialization
* ```tsx
* const useAuth = createGlobalState(
* () => ({ user: null, token: localStorage.getItem('token') }),
* {
* metadata: () => ({ createdAt: Date.now() }),
* }
* );
* ```
*/
<State, Metadata extends BaseMetadata, ActionsConfig extends ActionCollectionConfig<State, Metadata> | null | {}, PublicStateMutator = keyof ActionsConfig extends never | undefined ? React.Dispatch<React.SetStateAction<State>> : ActionCollectionResult<State, Metadata, NonNullable<ActionsConfig>>>(state: State | (() => State), args: {
name?: string;
metadata?: Metadata | (() => Metadata);
callbacks?: GlobalStoreCallbacks<Any, AnyActions, Any>;
actions?: ActionsConfig;
}): StateHook<State, PublicStateMutator, Metadata>;
/**
* Creates a global state hook that you can use across your application
* @param state initial state value or a callback function that returns the initial state
* @param args additional configuration for the global state
* @param args.name optional name for debugging purposes
* @param args.metadata optional non-reactive metadata associated with the state (can be a value or callback)
* @param args.callbacks optional lifecycle callbacks for the global state
* @param args.actions optional actions to restrict state mutations [if provided `setState` will be nullified]
* @returns a state hook that you can use in your components
*
* @example
* ```tsx
* const useCounter = createGlobalState(0, {
* actions: {
* increase() {
* return ({ setState }) => {
* setState((c) => c + 1);
* };
* },
* decrease(amount: number) {
* return ({ setState }) => {
* setState((c) => c - amount);
* };
* },
* },
* });
*
* function Counter() {
* const [count, {
* increase,
* decrease
* }] = useCounter();
*
* return (
* <div>
* <p>Count: {count}</p>
* <button onClick={increase}>
* Increment
* </button>
* <button onClick={() => {
* decrease(1);
* }}>
* Decrement
* </button>
* </div>
* );
* }
* ```
*/
<State, Metadata extends BaseMetadata, ActionsConfig extends ActionCollectionConfig<State, Metadata>, PublicStateMutator = ActionCollectionResult<State, Metadata, NonNullable<ActionsConfig>>>(state: State | (() => State), args: {
name?: string;
metadata?: Metadata | (() => Metadata);
callbacks?: GlobalStoreCallbacks<Any, AnyActions, Any>;
actions: ActionsConfig;
}): StateHook<State, PublicStateMutator, Metadata>;
}
/**
* Infers the actions type from a StateHook
* @example
* ```ts
* type CounterActions = InferActionsType<typeof useCounter>;
* ```
*/
export type InferActionsType<Hook extends StateHook<Any, Any, Any>> = ReturnType<Hook>['1'];
/**
* Infers the StoreTools type from a StateHook, useful to split actions code
*
* @example
* ```ts
* type CounterStoreTools = InferStateApi<typeof useCounter>;
* ```
*/
export type InferStateApi<Hook extends StateHook<Any, Any, Any>> = Hook extends StateHook<infer State, infer PublicStateMutator, infer Metadata> ? StoreTools<State, PublicStateMutator, Metadata> : never;
export declare const __uniqueIdBrand: unique symbol;
/**
* Branded unique identifier
*/
export type BrandedId<T extends string | undefined> = `${T extends string ? T : ''}${string}` & {
[__uniqueIdBrand]: T;
};
export interface UniqueId {
/**
* Generates a unique identifier string, optionally prefixed.
*
* @example
* uniqueId(); // "k9j3n5x8q2"
* type Id1 = `${string}` & { [__uniqueIdBrand]: undefined };
*
* uniqueId('user:'); // "user:k9j3n5x8q2"
* type Id2 = `user:${string}` & { [__uniqueIdBrand]: 'user:' };
*/
<T extends string | undefined>(prefix?: T): BrandedId<T>;
/**
* Creates a reusable unique ID generator for a specific prefix.
*
* @example
* const makeOrderId = uniqueId.for('order:');
* const id = makeOrderId(); // "order:k9j3n5x8q2"
* type OrderId = `order:${string}` & { [__brand]: 'order:' };
*/
for<T extends string>(prefix: T): {
(): `${T}${string}` & {
[__uniqueIdBrand]: T;
};
/**
* Checks if the given value matches the branded ID for this prefix.
*/
is(value: unknown): value is `${T}${string}` & {
[__uniqueIdBrand]: T;
};
/**
* Asserts that the value matches this branded ID, throws otherwise.
*/
assert(value: unknown): asserts value is `${T}${string}` & {
[__uniqueIdBrand]: T;
};
/**
* Returns a strictly branded generator using a custom symbol brand.
*/
strict<Brand extends symbol>(): {
(): `${T}${string}` & {
[__uniqueIdBrand]: Brand;
};
is(value: unknown): value is `${T}${string}` & {
[__uniqueIdBrand]: Brand;
};
assert(value: unknown): asserts value is `${T}${string}` & {
[__uniqueIdBrand]: Brand;
};
};
};
/**
* Creates a reusable unique ID generator without a prefix.
*/
of<T extends string>(): () => string & {
[__uniqueIdBrand]: T;
};
/**
* Creates a strictly branded unique ID generator without a prefix.
*/
strict<Brand extends symbol>(): () => string & {
[__uniqueIdBrand]: Brand;
};
}
/**
* Infers the appropriate API of the store
*/
export type InferAPI<T> = T extends React.Context<Any> ? InferContextApi<T> : T extends StateHook<Any, Any, Any> ? InferStateApi<T> : T extends {
Context: React.Context<Any>;
} ? InferContextApi<T['Context']> : never;
export type DerivedActionsConfig<ParentApi extends StoreTools<Any, Any, Any>> = {
readonly