import { useEffect , useReducer , useRef } from 'react' ; import axios , { AxiosRequestConfig } from 'axios' ; interface State < T > { status : 'init' | 'fetching' | 'error' | 'fetched' ; loading : boolean ; data ? : T ; error ? : string ; } interface Cache < T > { [ url ] : T ; } type Action < T > = { type : 'request' ; } | { type : 'success' ; payload : T ; } | { type : 'failure' ; payload : string ; } ; function useFetch < T = unknown > ( url ? , options ? ) { const cache = useRef < Cache < T > > ( { } ) ; let cancelRequest = false ; const initialState = { status : 'init' , loading : true , error : undefined , data : undefined , } ; const fetchReducer = ( state , action ) : State < T > => { switch ( action . type ) { case 'request' : return { ... initialState , status : 'fetching' , loading : true , } ; case 'success' : return { ... initialState , status : 'fetched' , data : action . payload , loading : false , } ; case 'failure' : return { ... initialState , status : 'error' , error : action . payload , loading : false } ; default : return state ; } } ; const [ state , dispatch ] = useReducer ( fetchReducer , initialState ) ; useEffect ( ( ) => { if ( ! url ) { return ; } const fetchData = async ( ) => { dispatch ( { type : 'request' } ) ; if ( cache . current [ url ] ) { dispatch ( { type : 'success' , payload : cache . current [ url ] } ) ; } else { try { const response = await axios ( url , options ) ; cache . current [ url ] = response . data ; if ( cancelRequest ) return ; dispatch ( { type : 'success' , payload : response . data } ) ; } catch ( error ) { if ( cancelRequest ) return ; dispatch ( { type : 'failure' , payload : error . message } ) ; } } } ; fetchData ( ) ; return ( ) => { cancelRequest = true ; } ; } , [ url ] ) ; return state ; } export default useFetch ;