import { useEffect , useState } from 'react' ; import { Future } from './future' ; export interface State < T > { get ( ) : T ; observe < U > ( selector , callback ) : ( ) => void ; set ( state ) : Promise < void > ; set ( updateFunction ) : Promise < void > ; useObserver < U > ( selector , dependencies ? ) : U ; useFutureObserver < U > ( selector , dependencies ? ) : Future < U | null > ; } export interface StateObserver < T , U > { callback : StateObserverCallback < U > ; lastSelectorResult ? : U ; selector : StateObserverSelector < T , U > ; } export type StateObserverCallback < T > = ( selectorResult : T ) => void ; export type StateObserverSelector < T , U > = ( state : T ) => U ; export type StatePartial < T > = { [ P in keyof T ] ? : T [ P ] ; } ; export const createState = < T > ( initialState ) : State < T > => { const data = initialState ; const observers = [ ] ; let isCallingObservers = false ; let observerTimers = null ; const get = ( ) : T => { return data ; } ; const set = async ( stateOrFunction ) : Promise < void > => { if ( isCallingObservers ) { console . warn ( 'Calling set while observers are being called should be avoided.' ) ; } let state ; if ( typeof stateOrFunction === 'function' ) { state = stateOrFunction . call ( null , data ) ; } else { state = stateOrFunction ; } Object . assign ( data , state ) ; return new Promise ( ( resolve ) => { resolve ( ) ; scheduleObservers ( ) ; } ) ; } ; const observe = < U > ( selector , callback ) : ( ( ) => void ) => { const observer = { selector , callback , lastSelectorResult : selector ( data ) , } ; observers . push ( observer ) ; const observerIndex = observers . length - 1 ; return ( ) => { if ( ! observers [ observerIndex ] ) { console . warn ( `No observer set on observerIndex` , observerIndex , initialState ) ; } observers [ observerIndex ] = null ; } ; } ; const scheduleObservers = ( ) => { if ( observerTimers ) { clearTimeout ( observerTimers ) ; } observerTimers = requestAnimationFrame ( callObservers ) ; } ; const callObservers = ( ) => { isCallingObservers = true ; observers . forEach ( ( observer ) => { try { if ( ! observer ) { return ; } const { callback , lastSelectorResult , selector } = observer ; const selectorResult = selector ( data ) ; if ( selectorResult !== lastSelectorResult ) { observer . lastSelectorResult = selectorResult ; callback ( selectorResult ) ; } } catch ( error ) { console . error ( error ) ; } } ) ; isCallingObservers = false ; } ; const useObserver = < U > ( selector , dependencies = [ ] ) : U => { const currentState = get ( ) ; const initialValue = selector . call ( null , currentState ) ; const [ data , setData ] = useState < U > ( initialValue ) ; useEffect ( ( ) => observe ( selector , ( data ) => setData ( data ) ) , [ ... dependencies , ] ) ; return data ; } ; const useFutureObserver = < U > ( selector , dependencies = [ ] ) : Future < U > => { const currentState = get ( ) ; const initialFuture = selector . call ( null , currentState ) ; const [ future , setData ] = useState < Future < U > > ( initialFuture ) ; useEffect ( ( ) => observe ( selector , ( future ) => setData ( future ) ) , [ ... dependencies ] ) ; return future ; } ; return { get , observe , set , useFutureObserver , useObserver } ; } ;