import { createContext as createContextOrig, createElement, useCallback, useContext as useContextOrig, useDebugValue, } from 'react'; import type { ComponentType, Context as ContextOrig, ReactNode } from 'react'; import { createContext, useContextSelector, useContextUpdate, } from 'use-context-selector'; import type { Context } from 'use-context-selector'; import { createTrackedSelector } from './createTrackedSelector.js'; const hasGlobalProcess = typeof process === 'object'; // eslint-disable-next-line @typescript-eslint/no-explicit-any type AnyFunction = (...args: any[]) => any; type Options = { defaultState?: State; defaultUpdate?: Update; stateContextName?: string; updateContextName?: string; concurrentMode?: boolean; }; /** * [Deprecated] Please use object option */ type DeprecatedOption = boolean; export const createContainer = ( useValue: (props: Props) => readonly [State, Update], options?: Options | DeprecatedOption, ) => { if (typeof options === 'boolean') { // eslint-disable-next-line no-console console.warn( 'boolean option is deprecated, please specify { concurrentMode: true }', ); options = { concurrentMode: options }; } const { stateContextName = 'StateContainer', updateContextName = 'UpdateContainer', concurrentMode, } = options || {}; const StateContext = createContext(options?.defaultState); const UpdateContext = createContextOrig( options?.defaultUpdate, ); StateContext.displayName = stateContextName; UpdateContext.displayName = updateContextName; const Provider = (props: Props & { children: ReactNode }) => { const [state, update] = useValue(props); return createElement( UpdateContext.Provider, { value: update }, createElement( StateContext.Provider as ComponentType<{ value: State; }>, { value: state }, props.children, ), ); }; const useSelector = (selector: (state: State) => Selected) => { if (hasGlobalProcess && process.env.NODE_ENV !== 'production') { const selectorOrig = selector; selector = (state: State) => { if (state === undefined) { throw new Error('Please use '); } return selectorOrig(state); }; } const selected = useContextSelector( StateContext as Context, selector, ); useDebugValue(selected); return selected; }; const useTrackedState = createTrackedSelector(useSelector); const useUpdate = concurrentMode ? () => { if ( hasGlobalProcess && process.env.NODE_ENV !== 'production' && useContextOrig(UpdateContext) === undefined ) { throw new Error('Please use '); } const contextUpdate = useContextUpdate( StateContext as Context, ); const update = useContextOrig(UpdateContext as ContextOrig); return useCallback( (...args: Parameters) => { let result: ReturnType | undefined; contextUpdate(() => { result = update(...args); }); return result as ReturnType; }, [contextUpdate, update], ); } : // not concurrentMode () => { if ( typeof process === 'object' && process.env.NODE_ENV !== 'production' && useContextOrig(UpdateContext) === undefined ) { throw new Error('Please use '); } return useContextOrig(UpdateContext as ContextOrig); }; const useTracked = () => [useTrackedState(), useUpdate()] as [ ReturnType, ReturnType, ]; return { Provider, useTrackedState, useTracked, useUpdate, useSelector, } as const; };