I promise this hook will blow your 1000+ lines of Async code
Tags: react, javascript, productivity, webdev
Published: 2021-12-02 07:22:30

---
series: [I promise this hook will blow your 1000+ lines of Async code]
---

No pun intended there! ;)

The `useAsync()` hook which I learned to build from Kent's Epic React Workshop looks like this:

```jsx
function useSafeDispatch(dispatch) {
  const mounted = React.useRef(false)

  React.useLayoutEffect(() => {
    mounted.current = true
    return () => (mounted.current = false)
  }, [])
  return React.useCallback(
    (...args) => (mounted.current ? dispatch(...args) : void 0),
    [dispatch],
  )
}

const defaultInitialState = {status: 'idle', data: null, error: null}

function useAsync(initialState) {
  const initialStateRef = React.useRef({
    ...defaultInitialState,
    ...initialState,
  })
  const [{status, data, error}, setState] = React.useReducer(
    (s, a) => ({...s, ...a}),
    initialStateRef.current,
  )

  const safeSetState = useSafeDispatch(setState)

  const setData = React.useCallback(
    data => safeSetState({data, status: 'resolved'}),
    [safeSetState],
  )
  const setError = React.useCallback(
    error => safeSetState({error, status: 'rejected'}),
    [safeSetState],
  )
  const reset = React.useCallback(
    () => safeSetState(initialStateRef.current),
    [safeSetState],
  )

  const run = React.useCallback(
    promise => {
      if (!promise || !promise.then) {
        throw new Error(
          `The argument passed to useAsync().run must be a promise. Maybe a function that's passed isn't returning anything?`,
        )
      }
      safeSetState({status: 'pending'})
      return promise.then(
        data => {
          setData(data)
          return data
        },
        error => {
          setError(error)
          return Promise.reject(error)
        },
      )
    },
    [safeSetState, setData, setError],
  )

  return {
    isIdle: status === 'idle',
    isLoading: status === 'pending',
    isError: status === 'rejected',
    isSuccess: status === 'resolved',

    setData,
    setError,
    error,
    status,
    data,
    run,
    reset,
  }
}

export {useAsync}
``` 

We will be using our hook to refactor the `BookInfo` component below and make it more elegant and robust by blowing multiple lines of code.💣

```jsx
import * as React from 'react'
import {
  fetchBook,
  BookInfoFallback,
  BookForm,
  BookDataView,
  ErrorFallback,
} from '../book'

function BookInfo({bookName}) {
  const [status, setStatus] = React.useState('idle')
  const [book, setBook] = React.useState(null)
  const [error, setError] = React.useState(null)

  React.useEffect(() => {
    if (!bookName) {
      return
    }
    setStatus('pending')
    fetchBook(bookName).then(
      book => {
        setBook(book)
        setStatus('resolved')
      },
      error => {
        setError(error)
        setStatus('rejected')
      },
    )
  }, [bookName])

  if (status === 'idle') {
    return 'Submit a book'
  } else if (status === 'pending') {
    return <BookInfoFallback name={bookName} />
  } else if (status === 'rejected') {
    return <ErrorFallback error={error}/>
  } else if (status === 'resolved') {
    return <BookDataView book={book} />
  }

  throw new Error('This should be impossible')
}

function App() {
  const [bookName, setBookName] = React.useState('')

  function handleSubmit(newBookName) {
    setBookName(newBookName)
  }

  return (
    <div className="book-info-app">
      <BookForm bookName={bookName} onSubmit={handleSubmit} />
      <hr />
      <div className="book-info">
        <BookInfo bookName={bookName} />
      </div>
    </div>
  )
}

export default App

``` 
I am suuppperrr excited, let's do this!


![Super Excited GIF](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0e3zynnspd64ebw8zjzh.gif)


But before we move ahead let's get on the same page. 

- 
`fetchBook` fetches data from the API and results in Promise which returns book data on resolution and error on rejection.

- 
`BookInfoFallback` is your loader component that accepts bookName to display a nice loading effect.

- 
`BookForm` is a simple form component that takes data from users.

- 
`BookDataView` is a nice looking component that displays the Book data to the user.

- 
`ErrorFallback` to show nice looking UI with Error.

Implementation of these components is beyond this blog but they are just regular stuff.

What the hell our code is doing?

It is taking the bookName from the user and passing that to the `BookInfo` component which handles fetching of the bookData in the `useEffect` hook which sets the state according to different conditions, it also handles the rendering of `BookDataView` upon successful fetching, `ErrorFallback` on failure, and `BookInfoFallback` while loading.

Ok I might have triggered 
> 
"Talk is cheap, show me the code" moment.


![Confused GIF](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tah9y1g2jfgrf6lra1g5.gif)

```jsx
import * as React from 'react'
import {
  fetchBook,
  BookInfoFallback,
  BookForm,
  BookDataView,
  ErrorFallback,
} from '../book'
import useAsync from '../utils';

function BookInfo({bookName}) {
  /////////////// Focus from here /////////////////
  const {data: book, isIdle, isLoading, isError, error, run} = useAsync()

  React.useEffect(() => {
     if (!pokemonName) {
       return
     }
     run(fetchPokemon(pokemonName))
  }, [pokemonName, run])

  if (isIdle) {
    return 'Submit a book'
  } else if (isLoading) {
    return <BookInfoFallback name={bookName} />
  } else if (isError) {
    return <ErrorFallback error={error}/>
  } else if (isSuccess) {
    return <BookDataView book={book} />
  }
 //////////////// To here /////////////////

  throw new Error('This should be impossible')
}

function App() {
  const [bookName, setBookName] = React.useState('')

  function handleSubmit(newBookName) {
    setBookName(newBookName)
  }

  return (
    <div className="book-info-app">
      <BookForm bookName={bookName} onSubmit={handleSubmit} />
      <hr />
      <div className="book-info">
        <BookInfo bookName={bookName} />
      </div>
    </div>
  )
}

export default App

``` 
Woah isn't that neat now, not only does it make our *code more readable*, we have made our component more *robust by not calling the dispatch when the component is unmounted*, also we have *memoized our fetch method* to save network calls if the bookName doesn't change.

But but Harsh aren't we writing more code to accomplish pretty common stuff?

Yes, we are but by writing that hook we can refactor multiple components throughout the project using Async code like that, see in terms of *cumulative time saved, less code shipped and high confidence gain.*

**This is the first part of the `useAsync()` hook which demonstrates its use cases.**

**In the next, we will decouple the hook and build it from scratch explaining each line and learning neat tricks.**

**We will also test the hook in part 3 because why not?**

*Are you excited about the real deal in part 2? Do tell in the comments and share this article with your friends and excite them too.*

A little intro about me, I want to make the world a better place through innovative and quality software.
Does that sound familiar?
Yeah, I am a big [Kent C. Dodds](https://twitter.com/kentcdodds) fan, he is an inspiration for many.

This hook is used extensively throughout his Epic React workshop. Go check out his awesome course  [here](https://epicreact.dev/).

I am also planning to share my learnings through such blogs in Future, Let's keep in touch!


[Portfolio](https://www.harshkc.tech) [Twitter](https://www.twitter.com/harshkc99) [Linkedin](https://www.linkedin.com/in/harshkc99) 

Also don't forget to check other blogs of the series!