Use an async effect
Custom hooks to use an async effect
function useAsync(asyncCallback) { let [state, dispatch] = React.useReducer(asyncReducer)
React.useEffect(() => { let promise = asyncCallback() if (!promise) return
dispatch({ type: 'pending' }) promise .then((data) => dispatch({ type: 'resolved', data })) .catch((error) => dispatch({ type: 'rejected', error })) }, [asyncCallback])
return state}Usage:
function Component({ input }) { // Remember to wrap the async job in a useCallback let asyncCallback = React.useCallback(() => { if (!input) return
// Run the async effect (fetch is an example) return fetch(input) }, [input])
let { status, data, error } = useAsync(asyncCallback)
switch (status) { case 'idle': return 'Waiting for the async to trigger' case 'pending': return 'Pending UI' case 'rejected': throw error case 'resolved': return 'Data UI' default: throw new Error('This should be impossible') }}How to clean the side effect (the async job start but then the component unmounted) ? - useSafeDispatch !
function useSafeDispatch(dispatch) { let mountedRef = React.useRef(false) React.useEffect(() => { mountedRef.current = true return () => { mountedRef.current = false } }, [])
return React.useCallback( (...args) => { if (mountedRef.current) { dispatch(...args) } }, [dispatch] )}Now change the useAsync function:
function useAsync(asyncCallback) { let [state, unsafeDispatch] = React.useReducer(asyncReducer) let dispatch = useSafeDispatch(unsafeDispatch)
React.useEffect(() => { let promise = asyncCallback() if (!promise) return
dispatch({ type: 'pending' }) promise .then((data) => dispatch({ type: 'resolved', data })) .catch((error) => dispatch({ type: 'rejected', error })) }, [asyncCallback])
return state}Cheers
react to this snippet