import { isFunction, Observable, observable } from '@legendapp/state'; import React, { MutableRefObject, Reducer, ReducerState } from 'react'; function overrideHooks(refObs: MutableRefObject | undefined>) { // @ts-expect-error Types don't match React's expected types React.useState = function useState(initialState: TRet | (() => TRet)) { const obs = refObs.current ?? (refObs.current = observable(isFunction(initialState) ? initialState() : initialState) as Observable); return [obs.get() as TRet, obs.set] as [TRet, React.Dispatch>]; }; // @ts-expect-error Types don't match React's expected types React.useReducer = function useReducer>( reducer: R, initializerArg: ReducerState, initializer: (arg: ReducerState) => ReducerState, ) { const obs = refObs.current ?? (refObs.current = observable( initializerArg !== undefined && isFunction(initializerArg) ? initializer(initializerArg) : initializerArg, ) as Observable); const dispatch = (action: any) => { obs.set(reducer(obs.get(), action)); }; return [obs, dispatch]; }; } export function createObservableHook( fn: (...args: TArgs) => TRet, ): (...args: TArgs) => Observable { const _useState = React.useState; const _useReducer = React.useReducer; return function (...args: TArgs) { const refObs = React.useRef>(); // First override the built-in hooks to create/update observables overrideHooks(refObs); // Then call the original hook fn(...args); // And reset back to the built-in hooks React.useState = _useState; React.useReducer = _useReducer; return refObs.current as Observable; }; }