Custom useFetch Hook in React

In this blog, I'm going to cover up all the advantages and problems occurred while creating this custom hook. You can use this hook into your app directly.
We are going to use useEffect() useState()and useRef() hooks of react.
The first step is simple and easily understandable.

Create file

Just create a new file named useFetch.js and in that create an arrow function and take api endpoint (url) as an argument.
Fetch the url response with async await method and store them in a react state and return the fetched data, simple!

export const useFetch = (url) => {
    const [data, setData] = useState(null);
    useEffect(() => {
        const fetchData = async () => {
                const response = await fetch(url)
                const json = await response.json();
                setData(json)
            }
        }
        fetchData();
    }, [url])
    return { data};
}

Creating function inside useEffect so that we don't have to pass it as a dependency and passing url as a dependency, if url changes it will trigger useEffect() to rerun and return data as an object.
Remember to take care of null state, while implementing this hook in your component.

1. Adding a LoadingPending state.

export const useFetch = (url) => {
    const [data, setData] = useState(null);
    const [isPending, setIsPending] = useState(false);

    useEffect(() => {

        const fetchData = async () => {
                setIsPending(true);
                const response = await fetch(url);
                const json = await response.json();
                setIsPending(false);
                setData(json);
            }
        }
        fetchData();
    }, [url])
    return { data, isPending};
}

You can use isPending and render any component (Eg: Loading Circle) whatever you want to.

2. Handling Error.

Create new state for handling error const [error,setError] = useState(null);

2.1 Handling Network Error.

Add try catch block.

  try{
          const response = await fetch(url);
          const json = await response.json();
          setData(json);
          setError(null); // incase if we have previous error
  }
catch(err){
  setError("Something Went Wrong!"); //Message for user
  console.log(err);  // log the error details on console
}
setIsPending(false);

Make isPending to false because whatever be the reason after try catch we are not fetching data anymore.

2.1 Handling Bad Request Error.

Using wrong url to fetch data may trigger this type of error.
Check response.ok property we get from fetch(url)

if(!response.ok) {
    throw new Error(response.statusText); // this will send you to the catch block with message of statusText
}

Return this error state , so that we can use this property in our component.
return { data, isPending, error};


Its not completed, here comes the main part of handling async request.
So, whenever you try to unmount the component while the async function is still fetching the data, React will give you warning.
To fix this issue cancel any kind of fetch request going on in cleanup function.

3. Aborting the fetch request.

To abort the fetch request we use cleanup function. Cleanup function is just a normal function we return inside useEffect() function and this function will triggered, when the component is unmounted during async request of this useEffect() function.

you can read more about AbortController

return () => {
  // Abort any async task or subscription using AbortController
  }

Create a new object of AbortController in useEffect().
const controller = new AbortController();
Associate this controller with fetch request you want to abort or else it won't know which request has to be aborted.
We can add signal property to the fetch method just like we pass options for GET and POST request.
const response = await fetch(url, { signal: controller.signal });
This line of code will associate our controller with fetch request we want to abort. Abort the request in return function.

return () => {
  // Abort any async task or subscription using AbortController
   controller.abort();
  }

After aborting, the fetch() method throws an special type of error called AbortError, that can be called inside catch block.
if( err.name === "AbortError" ) { // Do something }

Remember don't update the error state when request is aborted.

4. Avoiding infinite loop.

Do not use reference data types in dependency array of useEffect.
useEffect will get a new reference every time the code runs even if the value of reference type is same (unchanged).
When React re-evaluates component and compares new value to previous value it compares all reference type value.
To avoid this, assign reference type value to state or you can use the useRef() hook of react.

const options = useRef(_options).current;

You can find full code here.