Hey there, Fellow Coder! đ
So youâve mastered useState and useEffect. Now you want to build a real app. And real apps need real data.
Usually, this data lives on a server somewhere, and we need to go get it.
âEasy!â you say. âIâll just use fetch!â
Well, yes. But if you just slap a fetch inside a component, youâre inviting chaos.
What if the internet is slow? What if the server explodes? What if the user leaves the page before the data arrives?
Today, weâre going to learn how to fetch data professionally. Not just getting the data, but handling the User Experience (Loading & Errors).
Letâs dive in! đ
1. The Naive Approach (Donât do this in production)
Here is how most beginners start:
import { useState, useEffect } from "react";
const UserProfile = () => {
const [user, setUser] = useState(null);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users/1")
.then((res) => res.json())
.then((data) => setUser(data));
}, []);
if (!user) return <div>...</div>;
return <h1>{user.name}</h1>;
};
It works⊠on your fast local WiFi. But whatâs wrong?
- No Error Handling: If the API fails, the app crashes or shows nothing.
- Weak Loading State: Just returning
<div>...</div>impliesuseris null, but maybe we are just waiting?
2. The Robust Approach: 3 States
To build a bulletproof component, we need to track three things:
- Data: The result (if successful).
- Loading: Is the request still happening?
- Error: Did something go wrong?
Letâs rewrite it.
import { useState, useEffect } from "react";
const UserProfile = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true); // Start loading!
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/users/1");
if (!response.ok) {
throw new Error("Network response was not ok");
}
const result = await response.json();
setData(result);
setError(null);
} catch (err) {
setError(err.message);
setData(null);
} finally {
setLoading(false); // Whether success or fail, stop loading
}
};
fetchData();
}, []);
if (loading) return <div>Loading... âł</div>;
if (error) return <div>Error: {error} â ïž</div>;
return (
<div className="card">
<h1>{data.name}</h1>
<p>{data.email}</p>
</div>
);
};
See the difference? Now your user knows exactly whatâs happening.
- Loading? They see a spinner (or text).
- Error? They see a message, not a blank screen.
- Success? They see the content.
This is what differentiates a Junior from a Senior. Handling the âUnhappy Pathsâ.
3. Level Up: Custom Hook useFetch
Okay, that code above is great. But imagine copying those 3 states (data, loading, error) into every single component that needs data.
Thatâs messy. And repetitive.
Remember our rule? DRY (Donât Repeat Yourself).
Letâs extract this logic into a Custom Hook called useFetch.
// hooks/useFetch.js
import { useState, useEffect } from "react";
export const useFetch = (url) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const res = await fetch(url);
if (!res.ok) throw new Error(res.statusText);
const json = await res.json();
setData(json);
setError(null);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]); // Re-run if URL changes
return { data, loading, error };
};
Now look how clean our component becomes:
import { useFetch } from "./hooks/useFetch";
const UserProfile = () => {
const { data, loading, error } = useFetch(
"https://jsonplaceholder.typicode.com/users/1"
);
if (loading) return <div>Loading... âł</div>;
if (error) return <div>Error: {error} â ïž</div>;
return (
<div>
<h1>{data.name}</h1>
<p>{data.email}</p>
</div>
);
};
Boom! đ„
One line of code to handle fetching, loading, and error states.
You can reuse this useFetch hook in 100 different components.
Closing
Fetching data seems simple, but handling the user experience around it is an art.
- Always handle Loading states. Donât leave the user guessing.
- Always catch Errors. APIs fail. Be ready.
- Extract logic. If you do it twice, make a Hook.
Next time you need data, donât just fetch. useFetch.
Happy Coding! đ»â