- Published on
useAsync
Custom hooks to use an async effect
function useAsync(asyncCallback) { const [state, dispatch] = React.useReducer(asyncReducer)
React.useEffect(() => { const 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 const asyncCallback = React.useCallback(() => { if (!input) return
// Run the async effect (fetch is an example) return fetch(input) }, [input])
const { 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) { const 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) { const [state, unsafeDispatch] = React.useReducer(asyncReducer) const dispatch = useSafeDispatch(unsafeDispatch)
React.useEffect(() => { const 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