import React , { useEffect , useReducer , useRef , } from 'react' ; import { useLocation } from 'react-router' ; import { useParams } from 'react-router-dom' ; import loadMap , { load , LoadableState } from '../utils/loadable' ; const resolve = ( obj ) => ( obj && obj . __esModule ? obj . default : obj ) ; const render = ( loaded , props ) => React . createElement ( resolve ( loaded ) , props ) ; const createLoadableComponent = ( loadFn , options ) => { if ( ! options . loading ) { throw new Error ( 'loadable requires a `loading` component' ) ; } const opts = { render , path : '' , loader : null , loading : null , delay : 200 , timeout : 0 , webpack : [ ] , modules : [ ] , loadedData : null , selfManageNewProps : false , ... options , } ; /**
     * Cached res object so the promise or loading
     * is not executed multiple times
     */ let res ; const init = ( loadedData , props ) : LoadableState => { if ( ! res ) { res = loadFn ( opts . loader , loadedData , props ) ; } if ( ! res . promise ) { throw new Error ( 'Invalid state received from loadFn' ) ; } return res ; } ; const initialState = { loading : false , error : null , pastDelay : false , timedOut : false , } ; function reducer ( state = initialState , action ) { switch ( action . type ) { case 'SET_ERROR' : return { ... state , ... { error : action . error } } ; case 'SET_LOADING' : return { ... state , ... { loading : action . loading } } ; case 'SET_PAST_DELAY' : return { ... state , ... { pastDelay : action . pastDelay } } ; case 'SET_TIMED_OUT' : return { ... state , ... { timedOut : action . timedOut } } ; case 'UPDATE' : return { ... state , ... action . payload } ; default : return state ; } } const loadableComponent = ( ) => { const location = useLocation ( ) ; const params = useParams ( ) ; const props = { match : { params } } ; const previousRouterProps = useRef ( { location , params , } ) ; const resReference = useRef ( init ( undefined , props ) ) ; const [ loadableState , updateLoadableState , ] = useReducer ( reducer , { ... initialState , ... { timedOut : false , loading : resReference . current . loading , error : resReference . current . error , pastDelay : opts . delay <= 0 , } , } ) ; const previousCompleted = useRef ( resReference . current . completed ) ; const pastDelayTimeoutRef = useRef ( ) ; const timedOutTimeoutRef = useRef ( ) ; const isMounted = useRef ( false ) ; const isModuleLoading = useRef ( false ) ; const resetLoadableState = ( ) => { if ( isMounted . current ) { updateLoadableState ( { type : 'UPDATE' , payload : { timedOut : false , loading : resReference . current . loading , error : resReference . current . error , pastDelay : opts . delay <= 0 , } , } ) ; } } ; const clearTimeouts = ( ) => { if ( pastDelayTimeoutRef . current ) { clearTimeout ( pastDelayTimeoutRef . current ) ; } if ( timedOutTimeoutRef . current ) { clearTimeout ( timedOutTimeoutRef . current ) ; } } ; const loadModule = ( ) => { if ( ! resReference || ! resReference . current || ( resReference . current . completed && previousCompleted . current ) || ! resReference . current . promise || ! isMounted . current || isModuleLoading . current ) { return false ; } isModuleLoading . current = true ; clearTimeouts ( ) ; if ( opts . delay > 0 ) { pastDelayTimeoutRef . current = setTimeout ( ( ) => { if ( isMounted . current ) { isModuleLoading . current = false ; updateLoadableState ( { type : 'SET_PAST_DELAY' , pastDelay : true } ) ; } } , opts . delay ) ; } if ( opts . timeout ) { timedOutTimeoutRef . current = setTimeout ( ( ) => { if ( isMounted . current ) { isModuleLoading . current = false ; updateLoadableState ( { type : 'SET_TIMED_OUT' , timedOut : true } ) ; } } , opts . timeout ) ; } const update = ( ) => { if ( isMounted . current ) { clearTimeouts ( ) ; updateLoadableState ( { type : 'UPDATE' , payload : { timedOut : false , pastDelay : false , loading : resReference . current . loading , error : resReference . current . error , } , } ) ; } previousCompleted . current = resReference . current . completed ; isModuleLoading . current = false ; } ; resReference . current . promise . then ( update ) . catch ( update ) ; return true ; } ; const retry = ( ) => { previousCompleted . current = false ; res = loadFn ( opts . loader , undefined , props ) ; resReference . current = res ; resetLoadableState ( ) ; clearTimeouts ( ) ; if ( opts . delay > 0 ) { pastDelayTimeoutRef . current = setTimeout ( ( ) => { if ( isMounted . current ) { isModuleLoading . current = false ; updateLoadableState ( { type : 'SET_PAST_DELAY' , pastDelay : true } ) ; } } , opts . delay ) ; } if ( res . promise && res . promise . then ) { res . promise . then ( ( ) => { loadModule ( ) ; } ) ; } else { loadModule ( ) ; } } ; if ( ! opts . selfManageNewProps && previousRouterProps . current . location ?. key !== location . key ) { retry ( ) ; previousRouterProps . current = { location , params , } ; } else if ( ! previousRouterProps . current ) { previousRouterProps . current = { location , params , } ; } useEffect ( ( ) => { isMounted . current = true ; loadModule ( ) ; return ( ) => { res = undefined ; isMounted . current = false ; clearTimeouts ( ) ; } ; } , [ ] ) ; if ( loadableState . loading || resReference . current . loading || loadableState . error ) { if ( ! opts . loading ) return null ; return ( < opts . loading retry = { retry } pastDelay = { loadableState . pastDelay } timedOut = { loadableState . timedOut } error = { loadableState . error } / > ) ; } if ( resReference . current . completed ) { return opts . render ( resReference . current . resource , props ) ; } return null ; } ; loadableComponent . preload = init ; return loadableComponent ; } ; export default ( opts ) => createLoadableComponent ( load , opts ) ; const loadableMap = ( opts ) => { if ( typeof opts . render !== 'function' ) { throw new Error ( 'Loadable\'s Map requires a `render(loaded, props)` function' ) ; } return createLoadableComponent ( loadMap , opts ) ; } ; export { loadableMap as Map , } ;