After the render
Rendering should only *calculate* what to show. Talking to a server, starting a timer or changing the page title are side effects — they belong in an effect, which React runs *after* the screen updates.
useEffect
import { useEffect, useState } from "react";
// A pretend API (in a real app this would be fetch("/api/…")).
const DISHES = [
{ id: 1, name: "Momo", price: 180 },
{ id: 2, name: "Thukpa", price: 220 },
{ id: 3, name: "Sel roti", price: 60 },
];
function getDishes() {
return new Promise((resolve) => setTimeout(() => resolve(DISHES), 80));
}
function getDish(id) {
return new Promise((resolve, reject) =>
setTimeout(() => (DISHES.find((d) => d.id === id) ? resolve(DISHES.find((d) => d.id === id)) : reject(new Error("Not found"))), 80),
);
}
export default function Menu() {
const [dishes, setDishes] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
let ignore = false;
getDishes().then((data) => {
if (!ignore) {
setDishes(data);
setLoading(false);
}
});
return () => {
ignore = true;
};
}, []);
if (loading) return <p>Loading…</p>;
return <ul>{dishes.map((d) => <li key={d.id}>{d.name}</li>)}</ul>;
}- [] → run once after mount. [id] → run again whenever id changes. No array → after every render (rarely what you want).
- Every value from the component used inside the effect belongs in the array — React's linter checks this for you.
- Don't use an effect for things you can compute during render (like filtering a list).
- In bigger apps, libraries like TanStack Query or framework features (Next.js server components) handle data fetching for you.