In React, fetching data from an API is a common task that can be done efficiently using hooks. In this post, we’ll create a custom hook, useFetch
, which internally uses the useState
and useEffect
hooks to manage state and handle side effects.
Custom Hook with useState
and useEffect
To fetch data from any API using React’s built-in hooks, we can introduce a custom hook called useFetch
. This hook will manage the state and lifecycle of the data fetching operation, using useState
for tracking the data and loading state, and useEffect
for handling the asynchronous API call.
// useFetch.js
import { useState, useEffect } from "react";
const useFetch = (url) => {
const [state, setState] = useState([null, false]);
useEffect(() => {
setState([null, true]); // Set loading to true before the request
(async () => {
const data = await fetch(url).then((res) => res.json());
setState([data.body, false]); // Set the data and turn off loading
})();
}, [url]);
return state;
};
export default useFetch;
In this function, we return an array with two variables: the state (which holds the data) and the loading flag.
- Line 4: We initialize the state with
[null, false]
—null
for data andfalse
for the loading status. - Line 8: Once the API call is initiated, we set the loading state to
true
. - Line 11: After fetching the data using the
fetch()
function, we update the state by passing the fetched data as the first element and setting the loading flag tofalse
.
It’s important to note that you cannot directly call an asynchronous callback within useEffect
. That’s why we define a separate asynchronous function inside the useEffect
block.
In this example, we use an IIFE (Immediately Invoked Function Expression). This function is invoked immediately after its definition.
To use this hook in your component, simply call it like this:
// Import the custom useFetch hook
const Post = () => {
const [post, loading] = useFetch(
"https://jsonplaceholder.typicode.com/posts/1"
);
if (loading) {
return <Loading />;
}
return <PostBody content={post} />;
};
How to Handle API Errors
When fetching data from an API, you also need to handle potential errors. A traditional approach to handling success and errors in a Promise is to use .then()
and .catch()
.
import React, { useState, useEffect } from "react";
function FetchAPI() {
const [users, setUsers] = useState([]);
const [isError, setIsError] = useState(false);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => {
if (response.ok) {
return response.json();
} else {
throw "Error getting users list";
}
})
.then((data) => {
setUsers(data);
})
.catch((error) => {
setIsError(true);
});
}, []);
return (
<div>
{isError ? (
<h3>Error! Please try again later</h3>
) : (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)}
</div>
);
}
export default FetchAPI;
In this example, we used .then()
to handle the successful data fetching and .catch()
to manage errors. Note that the fetch
API does not automatically throw errors for HTTP status codes like 404
or 500
. You need to manually check for errors by inspecting the ok
property of the response object, or by checking the status code directly.
Using async-await
with try-catch
for Error Handling in useEffect
Another common approach for handling API requests is to use async-await
combined with try-catch
. This allows for cleaner, more readable code.
import React, { useState, useEffect } from "react";
function FetchAPI() {
const [users, setUsers] = useState([]);
const [isError, setIsError] = useState(false);
useEffect(() => {
const fetchData = async () => {
try {
let response = await fetch(
"https://jsonplaceholder.typicode.com/users"
);
if (response.status === 200) {
let data = await response.json();
setUsers(data);
} else {
throw "Error fetching users list";
}
} catch (error) {
setIsError(true);
}
};
fetchData();
}, []);
return (
<div>
{isError ? (
<h3>Error! Please try again later</h3>
) : (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)}
</div>
);
}
export default FetchAPI;
In this example, we replaced .then()
with await
and wrapped the entire request in a try-catch
block. This simplifies the code and provides a more synchronous-like flow. The error handling logic is done inside the catch
block, and we check for specific HTTP status codes to determine how to handle different responses.
Key Takeaways
- Custom Hook: The custom
useFetch
hook usesuseState
to store the fetched data and a loading flag, anduseEffect
to manage the side effects (i.e., making the API call). - Error Handling: You can handle errors using
.then()
and.catch()
orasync-await
withtry-catch
. - Best Practices: Always manage loading states and errors properly to ensure a smooth user experience when fetching data from APIs.
With this approach, you’ll be able to effectively fetch data from APIs using React hooks in a clean, efficient way.