This guide is to show you how to create a simple react hook for data fetching (with revalidation).
π€¨ Why this hook?
When fetching data for your react applications, you'd usually use both useState
and useEffect
, with values like loading
, data
and error
e.g This example, this hook is to help abstract that functionality into one simple hook that can be used anywhere and multiple times.
πΊ Setting up the project
We would be using the create-react-app
boiler template for typescript and the only external library we would be using is axios
for data fetching.
Open up your terminal and type in the following commands.
yarn create react-app use-fetch --template typescript
# for npm
npx create-react-app use-fetch --template typescript
Change into the directory and install axios
cd use-fetch
yarn add axios
# for npm
npm install axios
Within the src
directory delete the following file (because they aren't needed)
- App.css
- App.test.tsx
π£ Custom useFetch
hook
Within the src
directory create another directory called hooks
, this is where our hook will reside.
cd src
mkdir hooks
Your file structure should look something like this.
Within the hooks
directory create a file called useFetch.tsx
.
Type in the following inside the useFetch
file.
import { useState, useEffect, useCallback } from "react";
import axios from "axios";
interface UseFetchProps {
url: string;
}
const useFetch = ({ url }: UseFetchProps) => {
const [data, setData] = useState<any>();
const [error, setError] = useState(false);
// function to fetch data
const fetch = useCallback(async () => {
setError(false);
try {
const fetchedData = await axios.get(url);
setData(fetchedData.data);
} catch {
setError(true);
}
}, [url]);
useEffect(() => {
// on first load fetch data
fetch();
}, [fetch]);
return {
data,
error,
revalidate: fetch,
};
};
export default useFetch;
The hook takes in a prop url
, which is the API url at which we want to fetch data from. It has two states data
and error
which are used to store data gotten from the API and check for errors respectively.
We created a separate function for fetching the data called fetch
and wrapped it within a useCallback
hook, Visit here to see the reason why we used a useCallback
hook.
Then we simply used a useEffect
hook to run the fetch
function as soon as the hook is mounted π.
The hook returns data
, error
and revalidate
which is the fetch
function for when we want to programmatically revalidate the data.
π Using the hook
To use the hook we simply just import it and extract its values.
Within the App.tsx
import useFetch from "./hooks/useFetch";
import logo from "./logo.svg";
function App() {
const { error, data, revalidate } = useFetch({
url: "https://random-data-api.com/api/users/random_user?size=5",
});
if (!data) {
return <h2>Loading...</h2>;
}
if (error) {
return <h2>Error fetching users</h2>;
}
return (
<div className="App">
<img src={logo} alt="react logo" />
<h1 className="title">useFetch()</h1>
<button onClick={revalidate}>revalidate</button>
<div className="items">
{data.map((el: any) => (
<div className="item" key={el.uid}>
<img
src={`https://avatars.dicebear.com/api/big-smile/${el.first_name}.svg`}
alt={`${el.username} profile`}
className="item__img"
/>
<div className="item__info">
<p className="name">
{el.first_name} {el.last_name}{" "}
<span className="username">(@{el.username})</span>
</p>
<p className="job">{el.employment.title}</p>
<p
className={`status ${
el.subscription.status.toLowerCase() === "active"
? "success"
: el.subscription.status.toLowerCase() === "blocked"
? "danger"
: "warn"
}`}
>
{el.subscription.status}
</p>
</div>
</div>
))}
</div>
</div>
);
}
export default App;
β° Adding Interval revalidation
You might need to fetch data from your API every 5 seconds for revalidation (ensuring your data is up-to-date).
We need to add some modifications to our useFetch
hook. Lets and more props.
interface UseFetchProps {
url: string;
revalidate?: boolean;
interval?: number;
}
revalidate
will be a boolean to check if we want to implement interval revalidation or not, interval
will be the time taken between every revalidation (in seconds).
...
const useFetch = ({ url, revalidate, interval }: UseFetchProps) => {
...
We'll create a state called revalidateKey
that we will change on every interval which will be added to our useEffect
dependency array. Adding this to our dependency array will ensure that the function within our useEffect
will run everytime the revalidateKey
changes.
To change the revalidateKey
, we will create a new useEffect
that has a setInterval
.
...
const [revalidateKey, setRevalidateKey] = useState("");
...
useEffect(() => {
const revalidateInterval = setInterval(() => {
if (revalidate) {
setRevalidateKey(Math.random().toString());
}
// if no interval is given, use 3 seconds
}, (interval ? interval : 3) * 1000);
return () => clearInterval(revalidateInterval);
}, [interval, revalidate]);
Our useFetch
hook should then look something like this.
const useFetch = ({ url, revalidate, interval }: UseFetchProps) => {
const [revalidateKey, setRevalidateKey] = useState("");
const [data, setData] = useState<any>();
const [error, setError] = useState(false);
// function to fetch data
const fetch = useCallback(async () => {
setError(false);
try {
const fetchedData = await axios.get(url);
setData(fetchedData.data);
} catch {
setError(true);
}
}, [url]);
useEffect(() => {
const revalidateInterval = setInterval(() => {
if (revalidate) {
setRevalidateKey(Math.random().toString());
}
// if no interval is given, use 3 seconds
}, (interval ? interval : 3) * 1000);
return () => clearInterval(revalidateInterval);
}, [interval, revalidate]);
useEffect(() => {
// on first load fetch data and when revalidateKey changes
fetch();
}, [fetch, revalidateKey]);
return {
data,
error,
revalidate: fetch,
};
};
Using the useFetch
hook β¨
const { error, data, revalidate } = useFetch({
url: "https://random-data-api.com/api/users/random_user?size=5",
revalidate: false,
// fetch every 5 seconds
interval: 5,
});
β οΈ Graphql support
This hook uses only the GET
method, and Graphql uses POST
method for data fetching. To make the hook more dynamic you can add more props like isGraphql
and query
, isGraphql
will be a boolean to check if its Graphql or REST so you can have a condition in your hook to use axios.post()
instead of axios.get()
and query
for the graphql query.
Thank you for reading ππΎ, If you have any questions, additions, or subtractions please comment below.
The full source code is linked below ππ
Top comments (14)
Cool Work, But I'm agree with one of the user that could you add some cancel request.
Nice! Working with graphql I saw a hook called
useQuery
which returnsloading
,data
,error
andrefetch
.So, after some attempts to decouple the gql or rest implementation we've created a hook very similar to your
useFetch
which returns the four variables like theuseQuery
.I think that the
loading
flag could be useful for this hook too. You know, we can infer it if thedata
has a value but theloading
will help you to know if there is a "fetching" task in progressYeah, I kept thinking whether I should add a loading value or not, I initially did but I removed it, You can contribute to the repo.
Isn' that what SWR does? Why not go with Standards?
Itβs always fun to build and know how things work
I agree. But for production it's mostly better to go with the standards π€
From quick look of example code, it will show loading state for infinite time, if your api call fails.
It sets
error
to true, which displaysAre you sure? It looks like
data
would be falsy (if the fetch errors out!) and so your second if block can never run.git clone
the repo and look for yourself, if this is really an issue please add anissue
to the repo or try to contribute to it πThank you
Can u add cancel request when components is unmount
Hmmm, Could you add it as a GitHub issue