DEV Community

Cover image for Data fetching React Hook

Data fetching React Hook

Mirco Bellagamba on March 22, 2021

Making HTTP requests is a common task for most Single Page Applications. Due to the asynchronous nature of network requests, we need to manage the ...
Collapse
 
devhammed profile image
Hammed Oyedele

Nice article but you should look into AbortController for that subscribed part as this would also cancel the actual request if it is ongoing:

import * as React from "react"

export type RequestState<T> =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: T }
  | { status: "error"; error: Error }

export type RequestAction<T> =
  | { type: "start" }
  | { type: "completed"; data: T }
  | { type: "failed"; error: Error }

export function useFetch<T>(route: string): RequestState<T> {
  const [state, dispatch] = React.useReducer<
    React.Reducer<RequestState<T>, RequestAction<T>>
  >(reducer, { status: "idle" })
  React.useEffect(() => {
    const abortController = new AbortController()

    if (route) {
      dispatch({ type: "start" })
      fetch(route, { signal: abortController.signal })
        .then(response => {
          if (!response.ok) {
            throw new Error("Request failed")
          }
          return response.json()
        })
        .then(data => {
            dispatch({ type: "completed", data })
        })
        .catch(error => {
          if (error.name !== 'AbortError') {
            dispatch({ type: "failed", error })
          }
        })
    }
    return () => {
      abortController.abort()
    }
  }, [route])
  return state
}

export function reducer<T>(
  state: RequestState<T>,
  action: RequestAction<T>
): RequestState<T> {
  switch (action.type) {
    case "start":
      return {
        status: "loading",
      }
    case "completed":
      return {
        status: "success",
        data: action.data,
      }
    case "failed":
      return {
        status: "error",
        error: action.error,
      }
    default:
      return state
  }
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
mbellagamba profile image
Mirco Bellagamba

Thanks for the suggestion! This AbortController solution makes more explicit the cancel operation. Anyways, I don't use it very often because of the low browser support. Maybe, I should start using it shipping a polyfill for older browsers ๐Ÿค”.

Collapse
 
devhammed profile image
Hammed Oyedele

If you are worried about browser support, axios offers a similar API on top of XHR or go the polyfill way as you have said.

Collapse
 
sebalreves profile image
sebalreves

how can i use this hook for submitting a form? it gives me an error because it's calling inside the submitHandler function. Or it's better just to write an asynchronous function for this?

Collapse
 
mbellagamba profile image
Mirco Bellagamba

This hook cannot work for mutation requests, like form submissions, because it starts the fetch request as soon as the component is mounted. If you need to handle form submissions you should write a "classic" async handler.

Collapse
 
eecolor profile image
EECOLOR

Great tutorial!

Making fetch useful in more and more React scenario's will however become more complex over time. This video about react-query shows what I mean: youtube.com/watch?v=seU46c6Jz7E

I'm not saying you should use react-query and I encourage people to write things themselves before they grab a library. But it's nice to have an option in the back of your head when you reach the point where you are saying to yourself: "ouch, this is becoming too complex".

Collapse
 
mbellagamba profile image
Mirco Bellagamba

Thanks for the feedback! I know react-query and I agree with you that it's more suitable in complex web apps. That's what I wrote just before the "Connect" title ๐Ÿ˜Š, maybe you missed it.

In complex web apps, making a lot of network requests, requiring advanced features like caching, it will probably be better to use React Query, a powerful React data synchronization library.

Collapse
 
eecolor profile image
EECOLOR

Ahh yeah, I must have skipped over it.

Thread Thread
 
mbellagamba profile image
Mirco Bellagamba

Maybe I should have talked more about React Query because it is a very powerful library, as you said. Thanks to your comment, this tip has gotten the space it deserves ๐Ÿ˜ƒ.

Collapse
 
pankajpatel profile image
Pankaj Patel

Great article on useFetch

Thanks for adding TS and Testing around the hook

Collapse
 
bazen profile image
Bazen

nice article, the useFetch hook reusable.one thing to think about is you can also implement HOC say FetchHOC that renders the child component based in the state of the async action(calling API). that way your code will be even more reusable

Collapse
 
mbellagamba profile image
Mirco Bellagamba

Thanks for the suggestion! Generally speaking, I prefer the hooks approach because it's more explicit, but it's just a matter a preference. With an HOC we also need to handle props collision, if the component needs to make 2 request. Anyway, I'll try to refactor the hook in a HOC to see if it's even more practical.

Collapse
 
kuetabby profile image
Ivan

this is awesome thank you!

Collapse
 
mrezah1 profile image
MrezaH

perfect๐Ÿ‘ , thaknks๐Ÿ™