import { useCallback, useMemo, useReducer } from 'react'; export type LoadingValue = { error?: E; loading: boolean; reset: () => void; setError: (error: E) => void; setValue: (value?: T) => void; value?: T; }; type ReducerState = { error?: E; loading: boolean; value?: any; }; type ErrorAction = { type: 'error'; error: E }; type ResetAction = { type: 'reset'; defaultValue?: any }; type ValueAction = { type: 'value'; value: any }; type ReducerAction = ErrorAction | ResetAction | ValueAction; const defaultState = (defaultValue?: any) => { return { loading: defaultValue === undefined || defaultValue === null, value: defaultValue, }; }; const reducer = () => (state: ReducerState, action: ReducerAction): ReducerState => { switch (action.type) { case 'error': return { ...state, error: action.error, loading: false, value: undefined, }; case 'reset': return defaultState(action.defaultValue); case 'value': return { ...state, error: undefined, loading: false, value: action.value, }; default: return state; } }; export default (getDefaultValue?: () => T): LoadingValue => { const defaultValue = getDefaultValue ? getDefaultValue() : undefined; const [state, dispatch] = useReducer( reducer(), defaultState(defaultValue) ); const reset = useCallback(() => { const _defaultValue = getDefaultValue ? getDefaultValue() : undefined; dispatch({ type: 'reset', defaultValue: _defaultValue }); }, [getDefaultValue]); const setError = useCallback((error: E) => { dispatch({ type: 'error', error }); }, []); const setValue = useCallback((value?: T) => { dispatch({ type: 'value', value }); }, []); return useMemo( () => ({ error: state.error, loading: state.loading, reset, setError, setValue, value: state.value, }), [state.error, state.loading, reset, setError, setValue, state.value] ); };