Working with APIs
Most React apps need to fetch data from external APIs. Learn how to handle API requests, loading states, errors, and caching effectively.
🎯 Fetch API Basics
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
setUsers(data);
setLoading(false);
})
.catch(error => {
setError(error.message);
setLoading(false);
});
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
⚡ Async/Await Pattern
function Posts() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchPosts = async () => {
try {
const response = await fetch('https://api.example.com/posts');
if (!response.ok) throw new Error('Failed to fetch');
const data = await response.json();
setPosts(data);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
fetchPosts();
}, []);
return loading ? <Spinner /> : <PostList posts={posts} />;
}
📦 Axios Library
// npm install axios
import axios from 'axios';
// Create instance
const api = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
});
// Add auth token
api.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
function Component() {
const [data, setData] = useState(null);
useEffect(() => {
api.get('/users')
.then(response => setData(response.data))
.catch(error => console.error(error));
}, []);
return <div>{/* render data */}</div>;
}
🔄 CRUD Operations
function TodoManager() {
const [todos, setTodos] = useState([]);
// CREATE
const createTodo = async (text) => {
const response = await fetch('/api/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text, completed: false })
});
const newTodo = await response.json();
setTodos([...todos, newTodo]);
};
// READ
useEffect(() => {
fetch('/api/todos')
.then(res => res.json())
.then(setTodos);
}, []);
// UPDATE
const updateTodo = async (id, updates) => {
await fetch(`/api/todos/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates)
});
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, ...updates } : todo
));
};
// DELETE
const deleteTodo = async (id) => {
await fetch(`/api/todos/${id}`, { method: 'DELETE' });
setTodos(todos.filter(todo => todo.id !== id));
};
return (
<div>
{todos.map(todo => (
<div key={todo.id}>
<span>{todo.text}</span>
<button onClick={() => updateTodo(todo.id, { completed: !todo.completed })}>
Toggle
</button>
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</div>
))}
</div>
);
}
🎣 Custom useFetch Hook
function useFetch(url, options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
const fetchData = async () => {
setLoading(true);
try {
const response = await fetch(url, options);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const result = await response.json();
if (!cancelled) {
setData(result);
setError(null);
}
} catch (err) {
if (!cancelled) {
setError(err.message);
}
} finally {
if (!cancelled) {
setLoading(false);
}
}
};
fetchData();
return () => {
cancelled = true;
};
}, [url]);
return { data, loading, error };
}
// Usage
function UserProfile({ userId }) {
const { data: user, loading, error } = useFetch(`/api/users/${userId}`);
if (loading) return <Spinner />;
if (error) return <Error message={error} />;
return <div>{user.name}</div>;
}
🚀 React Query (TanStack Query)
// npm install @tanstack/react-query
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
function Posts() {
// Fetch data with automatic caching & refetching
const { data, isLoading, error } = useQuery({
queryKey: ['posts'],
queryFn: async () => {
const response = await fetch('/api/posts');
return response.json();
}
});
if (isLoading) return <Spinner />;
if (error) return <Error />;
return (
<ul>
{data.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
// Mutations
function CreatePost() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (newPost) => {
return fetch('/api/posts', {
method: 'POST',
body: JSON.stringify(newPost)
});
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] });
}
});
return (
<button onClick={() => mutation.mutate({ title: 'New Post' })}>
Add Post
</button>
);
}
⏱️ Polling & Auto-Refresh
function LiveData() {
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
const response = await fetch('/api/live-data');
const result = await response.json();
setData(result);
};
// Fetch immediately
fetchData();
// Poll every 5 seconds
const interval = setInterval(fetchData, 5000);
return () => clearInterval(interval);
}, []);
return <div>{data?.value}</div>;
}
// With React Query
function LiveDataRQ() {
const { data } = useQuery({
queryKey: ['liveData'],
queryFn: fetchLiveData,
refetchInterval: 5000 // Auto-refetch every 5s
});
return <div>{data?.value}</div>;
}
🔐 Authentication
// API service
const api = {
login: async (email, password) => {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
const data = await response.json();
localStorage.setItem('token', data.token);
return data;
},
fetchWithAuth: async (url, options = {}) => {
const token = localStorage.getItem('token');
const response = await fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${token}`
}
});
if (response.status === 401) {
// Token expired
localStorage.removeItem('token');
window.location.href = '/login';
}
return response.json();
}
};
// Usage
function Dashboard() {
const [data, setData] = useState(null);
useEffect(() => {
api.fetchWithAuth('/api/dashboard')
.then(setData);
}, []);
return <div>{/* dashboard */}</div>;
}
🎯 Key Takeaways
- useEffect: Fetch data on component mount
- Loading state: Show spinner while fetching
- Error handling: Catch and display errors
- Cleanup: Cancel requests on unmount
- Axios: Cleaner API than fetch
- React Query: Caching, refetching, mutations
- Authentication: Include tokens in headers
- CRUD: GET, POST, PATCH, DELETE methods