Fetching Data on Mount
Fetch JSON from an API inside useEffect, store the response in state, and trigger the fetch only once with an empty dependency array.
Fetching Data on Mount is a free Frontend Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Frontend Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
The Data Fetching Pattern
The standard React data fetching pattern: 1) define loading/error/data state, 2) fetch in useEffect with empty deps [], 3) update state in the callback, 4) render based on state.
Full Example
A complete example fetching users on mount with loading and error handling.
interface User { id: number; name: string; email: string; }
function UserList() {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetch('/api/users')
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
.then(data => setUsers(data))
.catch(err => setError(err.message))
.finally(() => setLoading(false));
}, []);
if (loading) return <Spinner />;
if (error) return <ErrorMessage message={error} />;
return <ul>{users.map(u => <UserItem key={u.id} {...u} />)}</ul>;
}Using async/await in useEffect
useEffect's callback can't be async directly (it would return a Promise, which React would interpret as a cleanup function). Instead, define an inner async function and call it immediately.
useEffect(() => {
async function fetchUsers() {
try {
const res = await fetch('/api/users');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
setUsers(data);
} catch (err) {
setError((err as Error).message);
} finally {
setLoading(false);
}
}
fetchUsers();
}, []);Checking Response Status
fetch doesn't throw on HTTP errors (4xx, 5xx) — only on network failures. Always check response.ok and throw manually for error responses.
const res = await fetch('/api/data');
if (!res.ok) {
// Could also use res.status for specific handling
throw new Error(`Server responded ${res.status}: ${res.statusText}`);
}
const data = await res.json();Fetching When a Dependency Changes
Add the dependency (like an ID from route params or props) to the effect's dependency array. The effect re-runs and fetches fresh data whenever the dependency changes.
const { userId } = useParams();
useEffect(() => {
if (!userId) return;
setLoading(true);
fetch(`/api/users/${userId}`)
.then(r => r.json())
.then(setUser)
.finally(() => setLoading(false));
}, [userId]);POST Requests in Effects
Effects can also send POST/PUT/DELETE requests, but these are usually triggered by events (form submit, button click), not mounted effects. Keep effects for data loading; use event handlers for mutations.
Custom useFetch Hook
Extract the fetch + loading + error pattern into a custom hook to reuse across components.
function useFetch<T>(url: string) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
setLoading(true);
fetch(url)
.then(r => { if (!r.ok) throw new Error(`${r.status}`); return r.json(); })
.then(setData)
.catch(e => setError(e.message))
.finally(() => setLoading(false));
}, [url]);
return { data, loading, error };
}Parsing JSON Safely
Always wrap res.json() in try/catch — malformed JSON throws a SyntaxError. The outer catch will handle it, but knowing the source helps debugging.
API Base URL Configuration
Don't hardcode API URLs. Use environment variables (import.meta.env.VITE_API_URL) for the base URL. Create a central api.ts file with a configured fetch wrapper.
const API_BASE = import.meta.env.VITE_API_URL;
async function apiFetch<T>(path: string): Promise<T> {
const res = await fetch(`${API_BASE}${path}`, {
headers: { 'Authorization': `Bearer ${getToken()}` }
});
if (!res.ok) throw new Error(`API error ${res.status}`);
return res.json();
}When to Move to React Query
Manual fetch patterns work for simple cases. When you need caching, background refetching, pagination, or mutations, move to React Query (TanStack Query) — it handles all these patterns reliably.
Showing a Loading Skeleton
Instead of a spinner, show skeleton UI that matches the shape of loaded content. This reduces perceived loading time and prevents layout shift.
Quick Check
Why can't you make a useEffect callback directly async?
Recap: Data Fetching in useEffect
Define loading/error/data state. Fetch in useEffect with empty deps []. Define an inner async function — don't make the callback async. Always check res.ok. Add URL/ID to deps for dynamic fetching. Extract into useFetch custom hook for reuse. Consider TanStack Query for production data fetching.
Frequently asked questions
Is the “Fetching Data on Mount” lesson free?
Yes — the full text of “Fetching Data on Mount” is free to read here on the web, and the Frontend Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Frontend Academy course, upgrade to CoddyKit PRO.
What will I learn in “Fetching Data on Mount”?
Fetch JSON from an API inside useEffect, store the response in state, and trigger the fetch only once with an empty dependency array. You practise Frontend Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Frontend Academy?
No prior experience is required. Frontend Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Fetching Data on Mount” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Frontend Academy lesson?
Yes. Every Frontend Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.