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