Chapter 06 - Exploring the World
Chapter 06 - Exploring the World
Hey everyone! Welcome back to Namaste React!
Today we break free from hardcoded data! We learn useEffect and make real API calls to fetch live restaurant data. This is where your apps start feeling like real production apps!
What we will cover:
- useEffect — the side effects hook
- The dependency array — [], [dep], and nothing
- Shimmer UI — skeleton loading screens
- Fetching data from a real API
- CORS — what it is and how to handle it
- Error handling in API calls
- Cleanup function — preventing memory leaks
- Abort Controller — cancelling fetch
- Interview Questions
1. useEffect — The Side Effects Hook
WHAT IS A SIDE EFFECT? ======================== Side effects are anything that reaches OUTSIDE the component's render: - Fetching data from an API - Setting up a timer (setTimeout, setInterval) - Manually updating the DOM - Setting document.title - Subscribing to a websocket - Reading from localStorage React's render function should be PURE: Same props/state → Same JSX output. No surprises. Side effects don't fit this model → useEffect gives them a home!
┌─────────────────────────────────────────────────────────────┐
│ useEffect SYNTAX │
└─────────────────────────────────────────────────────────────┘
useEffect(callback, [dependencyArray]);
// callback: the function with your side effect code
// dependencyArray: controls WHEN the effect runs
THREE FORMS:
1. No dependency array → runs after EVERY render
useEffect(() => {
console.log("Runs after every render!");
});
2. Empty array [] → runs ONCE after first render (mount only)
useEffect(() => {
console.log("Runs once, on mount!");
fetchRestaurants(); // ← API call goes here!
}, []);
3. With dependencies → runs when any dependency changes
useEffect(() => {
console.log("Runs when 'city' changes!");
fetchRestaurantsByCity(city);
}, [city]);
EXECUTION ORDER — when does useEffect fire?
=============================================
function App() {
console.log("A: Render starts");
useEffect(() => {
console.log("C: useEffect runs");
}, []);
console.log("B: Render ends, returning JSX");
return <div>Hello</div>;
}
OUTPUT on first render:
A: Render starts
B: Render ends, returning JSX
C: useEffect runs ← AFTER the component is painted!
The browser first PAINTS the component, THEN useEffect fires.
Users see the UI immediately, even before the API call starts!
2. Shimmer UI — Better UX While Loading
THE LOADING PROBLEM:
=====================
// ❌ Old way — show nothing until data loads:
if (loading) return <p>Loading...</p>; // Jarring! Plain text.
// ✅ Better way — Shimmer UI (skeleton screens):
// Show placeholder shapes that match the LAYOUT of the real content.
// Users know what's coming. Less jarring. Feels faster!
// Shimmer Card — placeholder for a restaurant card:
const ShimmerCard = () => (
<div className="shimmer-card">
<div className="shimmer-image"></div> {/* placeholder for image */}
<div className="shimmer-line"></div> {/* placeholder for name */}
<div className="shimmer-line short"></div> {/* placeholder for cuisine */}
<div className="shimmer-line short"></div> {/* placeholder for rating */}
</div>
);
// CSS for the shimmer animation:
// .shimmer-image { background: #e0e0e0; height: 200px; border-radius: 8px; }
// .shimmer-line { background: #e0e0e0; height: 16px; border-radius: 4px; margin: 8px 0; }
// .shimmer-line.short { width: 60%; }
// @keyframes shimmer { from { opacity: 0.6; } to { opacity: 1; } }
// .shimmer-card * { animation: shimmer 1s ease-in-out infinite alternate; }
// Shimmer Body — show multiple shimmer cards while loading:
const ShimmerBody = () => (
<div className="res-container">
{Array(8).fill("").map((_, i) => (
<ShimmerCard key={i} />
))}
</div>
);
3. Fetching Data from a Real API
// Complete Body component with real API call:
import { useEffect, useState } from "react";
const Body = () => {
const [listOfRestaurants, setListOfRestaurants] = useState([]);
const [filteredRestaurants, setFilteredRestaurants] = useState([]);
const [searchText, setSearchText] = useState("");
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetchData();
}, []); // ← empty array: only run once on mount
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch(
"https://www.swiggy.com/dapi/restaurants/list/v5?lat=12.9715987&lng=77.5945627"
);
const json = await response.json();
// Dig into the nested response structure
const restaurants = json?.data?.cards?.[4]?.card?.card?.gridElements?.infoWithStyle?.restaurants || [];
setListOfRestaurants(restaurants);
setFilteredRestaurants(restaurants);
} catch (err) {
setError("Failed to load restaurants. Please try again.");
console.error("API Error:", err);
} finally {
setLoading(false);
}
};
// Loading state — show shimmer
if (loading) return <ShimmerBody />;
// Error state
if (error) return (
<div className="error">
<p>{error}</p>
<button onClick={fetchData}>Retry</button>
</div>
);
// Empty state
if (listOfRestaurants.length === 0) return <p>No restaurants found!</p>;
return (
<div className="body">
<div className="search">
<input
value={searchText}
onChange={e => setSearchText(e.target.value)}
placeholder="Search restaurants..."
/>
<button onClick={handleSearch}>Search</button>
</div>
<div className="res-container">
{filteredRestaurants.map(res => (
<RestaurantCard key={res.info.id} resData={res} />
))}
</div>
</div>
);
};
4. CORS — What It Is and How to Handle It
┌─────────────────────────────────────────────────────────────┐
│ WHAT IS CORS? │
├─────────────────────────────────────────────────────────────┤
│ │
│ CORS = Cross-Origin Resource Sharing │
│ │
│ Browser security rule: │
│ A webpage at domain-A.com CANNOT freely fetch data │
│ from domain-B.com │
│ ...UNLESS domain-B.com explicitly allows it! │
│ │
│ Your React App: http://localhost:3000 │
│ Swiggy API: https://www.swiggy.com/dapi/... │
│ │
│ Different domains → CORS restriction! │
│ Browser BLOCKS the request and shows: │
│ "Access to fetch blocked by CORS policy" │
│ │
└─────────────────────────────────────────────────────────────┘
HOW CORS WORKS:
================
Browser sends a "preflight" OPTIONS request first:
OPTIONS /api/restaurants
Origin: http://localhost:3000
Server responds with allowed origins:
Access-Control-Allow-Origin: * (allow all)
Access-Control-Allow-Origin: http://localhost:3000 (allow specific)
If origin is allowed → browser proceeds with the real request!
If not → browser BLOCKS the request (CORS error)!
SOLUTIONS:
===========
1. Backend adds CORS headers (real fix):
res.setHeader("Access-Control-Allow-Origin", "*");
2. Proxy in development (Vite/Parcel):
// vite.config.js
server: {
proxy: {
"/api": "https://www.swiggy.com"
}
}
// Now /api/restaurants → https://www.swiggy.com/api/restaurants
// Same domain → no CORS!
3. Chrome extension (dev only):
"Allow CORS: Access-Control-Allow-Headers" extension
Only for local development — NEVER rely on this in production!
4. Use a proxy server (simple approach):
const response = await fetch(
"https://cors-anywhere.herokuapp.com/https://www.swiggy.com/..."
);
// cors-anywhere adds the CORS headers for you
5. Backend For Frontend (BFF) — proper production approach:
Your own Node.js server fetches Swiggy API → returns to React
No CORS issue (server-to-server has no CORS restrictions!)
5. Cleanup Function — Preventing Memory Leaks
// useEffect can return a CLEANUP function.
// React calls cleanup BEFORE running the effect again,
// AND when the component unmounts.
useEffect(() => {
// SETUP
const timer = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
// CLEANUP — runs before next effect OR on unmount
return () => {
clearInterval(timer); // ← prevents memory leak!
};
}, []); // Run once on mount, cleanup on unmount
// Without cleanup:
// Component mounts → timer starts
// Component unmounts (user navigates away)
// Timer is STILL RUNNING in memory!
// Timer tries to update state of an unmounted component!
// React Warning: "Can't perform a React state update on an unmounted component"
// This is a MEMORY LEAK!
// More examples:
// Websocket subscription:
useEffect(() => {
const ws = new WebSocket("wss://api.example.com");
ws.onmessage = (e) => setData(JSON.parse(e.data));
return () => ws.close(); // ← cleanup!
}, []);
// Event listener:
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize); // ← cleanup!
}, []);
6. Abort Controller — Cancelling Fetch on Unmount
// PROBLEM: Race condition with fast navigation
// User goes to page A → fetch starts
// User quickly goes to page B → page A unmounts
// Fetch from page A COMPLETES → tries to set state on UNMOUNTED component!
// Error: Can't update state on unmounted component!
// SOLUTION: AbortController
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
const fetchData = async () => {
try {
const response = await fetch(
"https://api.example.com/restaurants",
{ signal } // ← pass the signal to fetch!
);
const data = await response.json();
setRestaurants(data);
} catch (err) {
if (err.name === "AbortError") {
// Fetch was cancelled — this is expected, not a real error
console.log("Fetch cancelled");
return;
}
setError(err.message);
}
};
fetchData();
return () => {
controller.abort(); // ← cancel the fetch when component unmounts!
};
}, []);
Timeline:
──────────
Component mounts → fetch starts → AbortController created
User navigates away → component unmounts → cleanup runs → controller.abort()!
fetch is CANCELLED → no state update on unmounted component → no error! ✅
Interview Questions
Q: What is useEffect and what are side effects?
"useEffect is a React hook for performing side effects in functional components. Side effects are operations that reach outside the component's pure render flow — like fetching data, setting timers, updating the document title, or subscribing to events. useEffect accepts a callback and a dependency array that controls when it runs."
Q: Explain the dependency array in useEffect.
"The dependency array controls when useEffect re-runs. With an empty array ([]), the effect runs once on mount — perfect for initial data fetching. With dependencies ([city, userId]), the effect re-runs whenever those values change — perfect for fetching when filters change. With no array at all, the effect runs after every render — rarely what you want and can cause infinite loops if you update state inside it."
Q: What is a cleanup function in useEffect and why is it needed?
"The cleanup function is returned from the useEffect callback. React calls it before running the effect again (on dependency change) and when the component unmounts. It's needed to prevent memory leaks — clearing timers, closing websockets, removing event listeners, or aborting fetch requests. Without cleanup, resources keep running even after the component is gone."
Q: What is CORS and how do you handle it in React?
"CORS is a browser security mechanism that prevents a web page from making requests to a different domain than the one that served it. In React development, your app runs on localhost:3000 but APIs are on different domains. Solutions include: the backend setting Access-Control-Allow-Origin headers (proper fix), using a dev proxy in vite.config.js, or building a Backend-for-Frontend (BFF) proxy server that makes the API call server-side where CORS restrictions don't apply."
Q: What is Shimmer UI?
"Shimmer UI (also called skeleton screens) are placeholder components that match the layout of real content but show animated grey boxes instead. They're displayed during loading instead of a plain spinner or 'Loading...' text. They reduce perceived loading time because users can see the structure of what's coming, and the experience feels more polished and faster."
Key Points to Remember
| Concept | Key Takeaway |
|---|---|
| useEffect | For side effects (API calls, timers, subscriptions). Runs AFTER the component renders. |
| [] empty deps | Run once on mount. Best place for initial API calls. |
| [dep] deps | Re-runs whenever dep changes. Good for re-fetching when filter changes. |
| No deps | Runs after EVERY render. Rarely desired. Can cause infinite loops. |
| Cleanup function | Returned from callback. Clears timers, closes sockets, aborts fetch. Prevents leaks. |
| CORS | Browser blocks cross-domain requests. Fix via server headers, dev proxy, or BFF. |
| Shimmer UI | Skeleton loading screens that match layout. Better UX than "Loading..." text. |
| AbortController | Cancel in-flight fetch when component unmounts. Prevents state updates on dead components. |
What's Next?
In Chapter 07, we learn React Router — how to build multi-page experiences without page reloads. We'll add proper navigation to our food delivery app!
Keep coding, keep learning! See you in the next one!
Post a Comment