import { Dispatch , useCallback , useMemo , useRef , useState } from 'react' ; import { useFirstMountState } from './useFirstMountState' ; import { IHookStateInitAction , IHookStateSetAction , resolveHookState } from './misc/hookState' ; interface HistoryState < S > { history : S [ ] ; position : number ; capacity : number ; back : ( amount ? : number ) => void ; forward : ( amount ? : number ) => void ; go : ( position : number ) => void ; } export type UseStateHistoryReturn < S > = [ S , Dispatch < IHookStateSetAction < S > > , HistoryState < S > ] ; export function useStateWithHistory < S , I extends S > ( initialState , capacity ? , initialHistory ? ) ; export function useStateWithHistory < S = undefined > ( ) ; export function useStateWithHistory < S , I extends S > ( initialState ? , capacity = 10 , initialHistory ? ) { if ( capacity < 1 ) { throw new Error ( `Capacity has to be greater than 1, got '${ capacity }'` ) ; } const isFirstMount = useFirstMountState ( ) ; const [ state , innerSetState ] = useState < S > ( initialState as S ) ; const history = useRef < S [ ] > ( ( initialHistory ?? [ ] ) as S [ ] ) ; const historyPosition = useRef ( 0 ) ; if ( isFirstMount ) { if ( history . current . length ) { if ( history . current [ history . current . length - 1 ] !== initialState ) { history . current . push ( initialState as I ) ; } if ( history . current . length > capacity ) { history . current = history . current . slice ( history . current . length - capacity ) ; } } else { history . current . push ( initialState as I ) ; } historyPosition . current = history . current . length && history . current . length - 1 ; } const setState = useCallback ( ( newState ) : void => { innerSetState ( ( currentState ) => { newState = resolveHookState ( newState , currentState ) ; if ( newState !== currentState ) { if ( historyPosition . current < history . current . length - 1 ) { history . current = history . current . slice ( 0 , historyPosition . current + 1 ) ; } historyPosition . current = history . current . push ( newState as I ) - 1 ; if ( history . current . length > capacity ) { history . current = history . current . slice ( history . current . length - capacity ) ; } } return newState ; } ) ; } , [ state , capacity ] ) as Dispatch < IHookStateSetAction < S > > ; const historyState = useMemo ( ( ) => ( { history : history . current , position : historyPosition . current , capacity , back : ( amount = 1 ) => { if ( ! historyPosition . current ) { return ; } innerSetState ( ( ) => { historyPosition . current -= Math . min ( amount , historyPosition . current ) ; return history . current [ historyPosition . current ] ; } ) ; } , forward : ( amount = 1 ) => { if ( historyPosition . current === history . current . length - 1 ) { return ; } innerSetState ( ( ) => { historyPosition . current = Math . min ( historyPosition . current + amount , history . current . length - 1 ) ; return history . current [ historyPosition . current ] ; } ) ; } , go : ( position ) => { if ( position === historyPosition . current ) { return ; } innerSetState ( ( ) => { historyPosition . current = position < 0 ? Math . max ( history . current . length + position , 0 ) : Math . min ( history . current . length - 1 , position ) ; return history . current [ historyPosition . current ] ; } ) ; } , } ) , [ state ] ) ; return [ state , setState , historyState ] ; }