API Integration and Data Fetching
Connect your React app to backend services and handle asynchronous operations.
Understanding APIs and React: Connecting to the Outside World
Every modern web application needs to communicate with servers to get data, save information, or interact with other services. In React, we do this through APIs (Application Programming Interfaces). Think of an API as a waiter in a restaurant - you tell the waiter what you want, they go to the kitchen (server), and bring back your food (data).
What is an API? An API is like a menu at a restaurant:
- It tells you what's available (endpoints)
- How to order it (request methods)
- What you'll get back (response format)
- Any special requirements (authentication, headers)
Why Do We Need APIs?
- Get fresh data: User profiles, product listings, news articles
- Save information: Form submissions, user preferences, file uploads
- Real-time updates: Chat messages, notifications, live scores
- Third-party services: Maps, payment processing, social media
The Journey of Data
- Your React app needs some data (like a list of products)
- It makes a request to an API endpoint (like ordering from a menu)
- The server processes the request (kitchen prepares the order)
- Server sends back the data (waiter brings your food)
- React displays the data to the user (you enjoy your meal)
Common API Operations (CRUD)
- Create (POST): Add new data (create a new user account)
- Read (GET): Fetch existing data (get list of products)
- Update (PUT/PATCH): Modify existing data (update user profile)
- Delete (DELETE): Remove data (delete a comment)
What Makes API Calls Special in React?
- They're asynchronous (don't block the UI)
- They need proper state management
- They require loading indicators
- They need error handling
- They often happen in useEffect
1// 🌟 YOUR FIRST API CALL IN REACT23import React, { useState, useEffect } from 'react';45// Basic API call example6function MyFirstAPICall() {7 const [data, setData] = useState(null);8 const [loading, setLoading] = useState(false);9 const [error, setError] = useState(null);1011 const fetchData = async () => {12 setLoading(true);13 setError(null);1415 try {16 const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');1718 if (!response.ok) {19 throw new Error('Failed to fetch data');20 }2122 const json = await response.json();23 setData(json);24 } catch (err) {25 setError(err.message);26 } finally {27 setLoading(false);28 }29 };3031 return (32 <div>33 <button onClick={fetchData} disabled={loading}>34 {loading ? 'Loading...' : 'Fetch Data'}35 </button>3637 {error && <p style={{ color: 'red' }}>Error: {error}</p>}3839 {data && (40 <div>41 <h3>{data.title}</h3>42 <p>{data.body}</p>43 </div>44 )}45 </div>46 );47}4849// Using useEffect to fetch on mount50function AutoFetchExample() {51 const [users, setUsers] = useState([]);52 const [loading, setLoading] = useState(true);5354 useEffect(() => {55 // Fetch data when component mounts56 fetch('https://jsonplaceholder.typicode.com/users')57 .then(res => res.json())58 .then(data => {59 setUsers(data.slice(0, 5)); // Get first 5 users60 setLoading(false);61 })62 .catch(err => {63 console.error('Error:', err);64 setLoading(false);65 });66 }, []); // Empty dependency array = run once on mount6768 if (loading) return <div>Loading users...</div>;6970 return (71 <ul>72 {users.map(user => (73 <li key={user.id}>{user.name} - {user.email}</li>74 ))}75 </ul>76 );77}7879// Async/await vs Promises comparison80// Promise approach:81fetch(url)82 .then(response => response.json())83 .then(data => console.log(data))84 .catch(error => console.error(error));8586// Async/await approach (cleaner):87async function fetchData() {88 try {89 const response = await fetch(url);90 const data = await response.json();91 console.log(data);92 } catch (error) {93 console.error(error);94 }95}
Fetch API Basics: Making HTTP Requests
The Fetch API is the modern way to make HTTP requests in JavaScript. React applications commonly use it to communicate with backend APIs for data retrieval and submission.
Key Concepts:
- Promises: Fetch returns promises for asynchronous operations
- Response handling: Check status codes and parse response data
- Error handling: Handle network errors and API errors
- Request configuration: Set headers, methods, and body data
The Anatomy of a Fetch Request
fetch(url, options)
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
Understanding HTTP Methods
- GET: Retrieve data (like viewing a webpage)
- POST: Create new data (like submitting a form)
- PUT: Update entire resource (like editing a profile)
- PATCH: Update part of resource (like changing just email)
- DELETE: Remove data (like deleting a comment)
Response Status Codes
- 200-299: Success! Everything worked
- 300-399: Redirection - resource moved
- 400-499: Client error - you did something wrong
- 500-599: Server error - server had a problem
Headers: Extra Information Headers are like metadata for your request:
- Content-Type: What format is the data? (JSON, form data, etc.)
- Authorization: Who are you? (API keys, tokens)
- Accept: What format do you want back?
1import { useState, useEffect } from 'react';23// 🌟 FETCH API EXAMPLES45// GET Request6function GetExample() {7 const [users, setUsers] = useState([]);8 const [loading, setLoading] = useState(true);910 useEffect(() => {11 fetch('https://jsonplaceholder.typicode.com/users')12 .then(response => {13 if (!response.ok) {14 throw new Error(`HTTP error! status: ${response.status}`);15 }16 return response.json();17 })18 .then(data => setUsers(data))19 .catch(error => console.error('Error:', error))20 .finally(() => setLoading(false));21 }, []);2223 if (loading) return <div>Loading...</div>;2425 return (26 <ul>27 {users.map(user => (28 <li key={user.id}>{user.name}</li>29 ))}30 </ul>31 );32}3334// POST Request35async function createPost(title, body) {36 const response = await fetch('https://jsonplaceholder.typicode.com/posts', {37 method: 'POST',38 headers: {39 'Content-Type': 'application/json',40 },41 body: JSON.stringify({42 title,43 body,44 userId: 145 })46 });4748 if (!response.ok) {49 throw new Error('Failed to create post');50 }5152 return response.json();53}5455// PUT Request (Update)56async function updatePost(id, updates) {57 const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, {58 method: 'PUT',59 headers: {60 'Content-Type': 'application/json',61 },62 body: JSON.stringify(updates)63 });6465 return response.json();66}6768// DELETE Request69async function deletePost(id) {70 const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, {71 method: 'DELETE'72 });7374 if (!response.ok) {75 throw new Error('Failed to delete');76 }77}7879// Working with Headers80async function fetchWithAuth(token) {81 const response = await fetch('https://api.example.com/protected', {82 headers: {83 'Authorization': `Bearer ${token}`,84 'Accept': 'application/json'85 }86 });8788 return response.json();89}9091// Query Parameters92function buildUrl(base, params) {93 const url = new URL(base);94 Object.keys(params).forEach(key =>95 url.searchParams.append(key, params[key])96 );97 return url.toString();98}99100// Usage:101const url = buildUrl('https://api.example.com/users', {102 page: 1,103 limit: 10,104 sort: 'name'105});106// Result: https://api.example.com/users?page=1&limit=10&sort=name107108// Abort Controller (Cancel requests)109function CancelableRequest() {110 const [data, setData] = useState(null);111112 useEffect(() => {113 const controller = new AbortController();114115 fetch('https://api.example.com/data', {116 signal: controller.signal117 })118 .then(res => res.json())119 .then(data => setData(data))120 .catch(err => {121 if (err.name === 'AbortError') {122 console.log('Request was cancelled');123 }124 });125126 // Cleanup: cancel request if component unmounts127 return () => controller.abort();128 }, []);129130 return <div>{data ? JSON.stringify(data) : 'Loading...'}</div>;131}
Loading States and Error Handling: Creating Great User Experience
Proper loading states and error handling create a better user experience by providing feedback during API operations and gracefully handling failures.
Why Loading States Matter Users need to know something is happening! Without loading states:
- Users might click buttons multiple times
- They might think the app is broken
- They might leave your site
- It looks unprofessional
Types of Loading States
- Initial load: First time loading data
- Refresh: Updating existing data
- Action feedback: Response to user actions
- Skeleton screens: Show UI structure while loading
- Progressive loading: Load critical data first
Error Handling Best Practices
- Always expect things to go wrong
- Provide clear error messages
- Offer ways to recover (retry buttons)
- Log errors for debugging
- Don't expose sensitive information
Common Error Scenarios
- Network failures (no internet)
- Server errors (500 errors)
- Invalid data (400 errors)
- Timeout errors
- Permission errors (401, 403)
1import { useState, useEffect } from 'react';23// 🌟 LOADING STATES AND ERROR HANDLING45// Custom hook for API calls6function useApi(url) {7 const [data, setData] = useState(null);8 const [loading, setLoading] = useState(true);9 const [error, setError] = useState(null);1011 useEffect(() => {12 const fetchData = async () => {13 try {14 setLoading(true);15 const response = await fetch(url);1617 if (!response.ok) {18 throw new Error(`HTTP error! status: ${response.status}`);19 }2021 const json = await response.json();22 setData(json);23 } catch (err) {24 setError(err.message);25 } finally {26 setLoading(false);27 }28 };2930 fetchData();31 }, [url]);3233 return { data, loading, error };34}3536// Example component using the hook37function UserProfile({ userId }) {38 const { data: user, loading, error } = useApi(39 `https://jsonplaceholder.typicode.com/users/${userId}`40 );4142 if (loading) {43 return <div>Loading user data...</div>;44 }4546 if (error) {47 return (48 <div style={{ color: 'red' }}>49 Error loading user: {error}50 </div>51 );52 }5354 return (55 <div>56 <h2>{user.name}</h2>57 <p>Email: {user.email}</p>58 <p>Phone: {user.phone}</p>59 </div>60 );61}6263// Loading spinner component64function LoadingSpinner() {65 return (66 <div className="spinner">67 <div className="spinner-circle"></div>68 <style jsx>{`69 .spinner {70 display: flex;71 justify-content: center;72 padding: 20px;73 }74 .spinner-circle {75 width: 40px;76 height: 40px;77 border: 4px solid #f3f3f3;78 border-top: 4px solid #3498db;79 border-radius: 50%;80 animation: spin 1s linear infinite;81 }82 @keyframes spin {83 0% { transform: rotate(0deg); }84 100% { transform: rotate(360deg); }85 }86 `}</style>87 </div>88 );89}9091// Error handling patterns92function ErrorHandling() {93 const [data, setData] = useState(null);94 const [error, setError] = useState(null);95 const [retryCount, setRetryCount] = useState(0);9697 const fetchData = async () => {98 try {99 setError(null);100 const response = await fetch('/api/data');101102 if (!response.ok) {103 // Handle different error codes104 if (response.status === 404) {105 throw new Error('Data not found');106 } else if (response.status === 401) {107 throw new Error('Please login to continue');108 } else if (response.status >= 500) {109 throw new Error('Server error. Please try again later.');110 } else {111 throw new Error('Something went wrong');112 }113 }114115 const data = await response.json();116 setData(data);117 } catch (err) {118 setError(err.message);119 // Log to error tracking service120 console.error('API Error:', err);121 }122 };123124 const retry = () => {125 setRetryCount(count => count + 1);126 fetchData();127 };128129 if (error) {130 return (131 <div className="error-container">132 <h3>⚠️ Error</h3>133 <p>{error}</p>134 <button onClick={retry}>135 Try Again ({retryCount})136 </button>137 </div>138 );139 }140141 return <div>{/* Your content */}</div>;142}143144// Different loading states145function LoadingStates() {146 const [loadingType, setLoadingType] = useState('spinner');147148 const loadingComponents = {149 spinner: <LoadingSpinner />,150 skeleton: (151 <div className="skeleton">152 <div className="skeleton-line"></div>153 <div className="skeleton-line short"></div>154 <style jsx>{`155 .skeleton-line {156 height: 20px;157 background: #e0e0e0;158 margin: 10px 0;159 border-radius: 4px;160 animation: pulse 1.5s ease-in-out infinite;161 }162 .skeleton-line.short {163 width: 60%;164 }165 @keyframes pulse {166 0% { opacity: 0.6; }167 50% { opacity: 1; }168 100% { opacity: 0.6; }169 }170 `}</style>171 </div>172 ),173 message: <p>Loading, please wait...</p>174 };175176 return loadingComponents[loadingType];177}
Data Caching and Optimization: Making Your App Fast
Data caching improves performance by storing frequently accessed data and reducing unnecessary API calls. This is especially important for data that doesn't change often.
What is Caching? Caching is like keeping a notebook of answers to common questions. Instead of asking the same question repeatedly, you check your notebook first. If the answer is there and still fresh, you use it. Otherwise, you ask again and update your notebook.
Benefits of Caching
- Faster load times: Instant data from memory
- Reduced server load: Fewer API calls
- Better offline experience: Show cached data when offline
- Lower costs: Less bandwidth usage
- Improved user experience: No waiting for repeated requests
Caching Strategies
- Time-based (TTL): Cache expires after certain time
- Event-based: Clear cache on specific actions
- Size-based: Limit cache size, remove oldest
- Conditional: Cache only certain types of data
When to Cache
- Data that rarely changes (user profiles, settings)
- Expensive calculations
- Frequently accessed data
- Reference data (countries, categories)
When NOT to Cache
- Real-time data (stock prices, live scores)
- Sensitive data (passwords, tokens)
- User-specific temporary data
- Data that changes frequently
1import { useState, useEffect, useCallback, useRef } from 'react';23// 🌟 SIMPLE CACHE IMPLEMENTATION45class SimpleCache {6 constructor() {7 this.cache = new Map();8 }910 set(key, value, ttl = 5 * 60 * 1000) { // Default 5 minutes11 this.cache.set(key, {12 value,13 expiry: Date.now() + ttl14 });15 }1617 get(key) {18 const item = this.cache.get(key);19 if (!item) return null;2021 // Check if expired22 if (Date.now() > item.expiry) {23 this.cache.delete(key);24 return null;25 }2627 return item.value;28 }2930 clear() {31 this.cache.clear();32 }33}3435const apiCache = new SimpleCache();3637// 🚀 CUSTOM CACHING HOOK3839function useCachedApi(url, options = {}) {40 const [data, setData] = useState(null);41 const [loading, setLoading] = useState(true);42 const [error, setError] = useState(null);43 const [isFromCache, setIsFromCache] = useState(false);4445 const {46 cacheKey = url,47 cacheTTL = 5 * 60 * 1000, // 5 minutes default48 skipCache = false49 } = options;5051 const fetchData = useCallback(async (forceRefresh = false) => {52 try {53 // Check cache first54 if (!forceRefresh && !skipCache) {55 const cachedData = apiCache.get(cacheKey);56 if (cachedData) {57 setData(cachedData);58 setIsFromCache(true);59 setLoading(false);60 return cachedData;61 }62 }6364 setLoading(true);65 setError(null);66 setIsFromCache(false);6768 const response = await fetch(url);6970 if (!response.ok) {71 throw new Error(`HTTP error! status: ${response.status}`);72 }7374 const jsonData = await response.json();7576 // Cache the successful response77 if (!skipCache) {78 apiCache.set(cacheKey, jsonData, cacheTTL);79 }8081 setData(jsonData);82 return jsonData;83 } catch (err) {84 setError(err);8586 // Try to use stale cache data on error87 const staleData = apiCache.get(cacheKey);88 if (staleData) {89 setData(staleData);90 setIsFromCache(true);91 }92 } finally {93 setLoading(false);94 }95 }, [url, cacheKey, cacheTTL, skipCache]);9697 useEffect(() => {98 fetchData();99 }, [fetchData]);100101 const refresh = () => fetchData(true);102103 return { data, loading, error, isFromCache, refresh };104}105106// 🛍️ EXAMPLE: PRODUCT LIST WITH CACHING107108function ProductList() {109 const [selectedCategory, setSelectedCategory] = useState('all');110111 const url = selectedCategory === 'all'112 ? 'https://fakestoreapi.com/products?limit=6'113 : `https://fakestoreapi.com/products/category/${selectedCategory}?limit=6`;114115 const {116 data: products,117 loading,118 error,119 isFromCache,120 refresh121 } = useCachedApi(url, {122 cacheKey: `products-${selectedCategory}`,123 cacheTTL: 10 * 60 * 1000 // Cache for 10 minutes124 });125126 return (127 <div>128 <h2>Product Catalog</h2>129130 {/* Cache indicator */}131 <div style={{132 padding: '10px',133 marginBottom: '20px',134 backgroundColor: isFromCache ? '#d4edda' : '#cce5ff',135 borderRadius: '5px'136 }}>137 {isFromCache ? '📦 Cached data' : '🌐 Fresh data'}138 </div>139140 {/* Category filter */}141 <select142 value={selectedCategory}143 onChange={(e) => setSelectedCategory(e.target.value)}144 >145 <option value="all">All Products</option>146 <option value="electronics">Electronics</option>147 <option value="jewelery">Jewelery</option>148 </select>149150 <button onClick={refresh} style={{ marginLeft: '10px' }}>151 🔄 Refresh152 </button>153154 {loading && !products && <div>Loading...</div>}155 {error && <div style={{ color: 'red' }}>Error: {error.message}</div>}156157 <div style={{158 display: 'grid',159 gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))',160 gap: '20px',161 marginTop: '20px'162 }}>163 {products?.map(product => (164 <div key={product.id} style={{165 border: '1px solid #ddd',166 padding: '10px',167 borderRadius: '8px'168 }}>169 <h4>{product.title.substring(0, 50)}...</h4>170 <p style={{ fontWeight: 'bold', color: '#28a745' }}>171 ${product.price}172 </p>173 </div>174 ))}175 </div>176 </div>177 );178}179180// 🚀 OPTIMISTIC UPDATES EXAMPLE181182function TodoListOptimistic() {183 const [todos, setTodos] = useState([184 { id: 1, title: 'Learn React', completed: false },185 { id: 2, title: 'Build an app', completed: false }186 ]);187188 const toggleTodo = async (id) => {189 // Optimistically update UI190 setTodos(prev => prev.map(todo =>191 todo.id === id ? { ...todo, completed: !todo.completed } : todo192 ));193194 try {195 // Simulate API call196 await new Promise(resolve => setTimeout(resolve, 1000));197 // In real app, you'd make actual API call here198 } catch (error) {199 // Revert on error200 setTodos(prev => prev.map(todo =>201 todo.id === id ? { ...todo, completed: !todo.completed } : todo202 ));203 }204 };205206 return (207 <div>208 <h3>Todo List (Optimistic Updates)</h3>209 <ul style={{ listStyle: 'none', padding: 0 }}>210 {todos.map(todo => (211 <li key={todo.id} style={{ marginBottom: '10px' }}>212 <label style={{ cursor: 'pointer' }}>213 <input214 type="checkbox"215 checked={todo.completed}216 onChange={() => toggleTodo(todo.id)}217 style={{ marginRight: '10px' }}218 />219 <span style={{220 textDecoration: todo.completed ? 'line-through' : 'none'221 }}>222 {todo.title}223 </span>224 </label>225 </li>226 ))}227 </ul>228 </div>229 );230}231232// 💡 CACHING BEST PRACTICES:233// 1. Set appropriate TTL (Time To Live)234// 2. Clear cache on user actions (logout, data updates)235// 3. Handle stale data gracefully236// 4. Use meaningful cache keys237// 5. Consider using React Query or SWR for production apps