Fetching Data in React
React does not have a built-in data fetching solution. The two most common approaches are the native fetch API and the Axios library. Both are typically used inside useEffect or a custom hook.
fetch API
// GET request
useEffect(() => {
fetch("https://api.example.com/posts")
.then(res => {
if (!res.ok) throw new Error(`HTTP error: ${res.status}`);
return res.json();
})
.then(data => setPosts(data))
.catch(err => setError(err.message));
}, []);
// POST request with JSON body
const createPost = async (newPost) => {
const res = await fetch("https://api.example.com/posts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(newPost),
});
if (!res.ok) throw new Error("Failed to create post");
return res.json();
};
Axios
npm install axios
import axios from "axios";
// Create an instance with base URL and default headers
const api = axios.create({
baseURL: "https://api.example.com",
headers: { "Content-Type": "application/json" },
timeout: 10000,
});
// Add auth token to every request
api.interceptors.request.use(config => {
const token = localStorage.getItem("token");
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
});
// GET
const { data: posts } = await api.get("/posts");
// POST
const { data: newPost } = await api.post("/posts", { title, body, userId });
// PUT
const { data: updated } = await api.put(`/posts/${id}`, payload);
// DELETE
await api.delete(`/posts/${id}`);
TanStack Query (React Query) โ Recommended
For production apps, TanStack Query is the gold standard. It handles caching, loading states, error states, background refetching, and optimistic updates automatically.
npm install @tanstack/react-query
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
// Fetch with automatic caching and loading state
function PostList() {
const { data: posts, isPending, isError } = useQuery({
queryKey: ["posts"],
queryFn: () => api.get("/posts").then(r => r.data),
staleTime: 5 * 60 * 1000, // cache for 5 minutes
});
if (isPending) return <Spinner />;
if (isError) return <p>Failed to load posts.</p>;
return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>;
}
// Mutation with cache invalidation
function CreatePostForm() {
const qc = useQueryClient();
const mutation = useMutation({
mutationFn: (data) => api.post("/posts", data).then(r => r.data),
onSuccess: () => qc.invalidateQueries({ queryKey: ["posts"] }),
});
return <button onClick={() => mutation.mutate({ title: "New Post" })}>Create</button>;
}
NoteTanStack Query replaces the need for manual loading/error state in most components. It’s the recommended approach for any app that talks to an API.