Expert · React concepts · topic 60 of 64 · 40 min
Effects, data fetching and hooks
Explanation
What is an effect?
Rendering should be pure: work out the JSX from props and state, nothing else. But some code must talk to the world outside React: set a timer, change document.title, connect to a chat server, or fetch data. useEffect runs such code after React has updated the screen. Its job is to keep the component in sync with an outside system.
import { useEffect, useState } from 'react';
function CartTitle({ count }) {
useEffect(() => {
document.title = `Cart (${count})`;
}, [count]);
return <h1>Your cart</h1>;
}The dependency array
| You write | The effect runs |
|---|---|
| useEffect(fn, []) | After the first render only (when the component appears) |
| useEffect(fn, [city]) | After the first render, and again after any render where city changed |
| useEffect(fn) | After every render (rarely what you want) |
List every value from the component that the effect reads: props, state and functions declared inside the component. React compares each one with Object.is. React's official lint plugin, eslint-plugin-react-hooks, warns you when you forget one. Do not silence it; a missing dependency means the effect uses old values.
Cleanup: return a function that stops the work
function OtpTimer() {
const [seconds, setSeconds] = useState(30);
useEffect(() => {
const id = setInterval(() => {
setSeconds((s) => (s > 0 ? s - 1 : 0));
}, 1000);
return () => clearInterval(id); // cleanup
}, []);
return <p>Resend OTP in {seconds}s</p>;
}Fetching data: loading, error and the ignore flag
function TrainStatus({ trainNo }) {
const [status, setStatus] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let ignore = false;
setLoading(true);
setError(null);
async function load() {
try {
const res = await fetch(`/api/trains/${trainNo}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
if (!ignore) setStatus(data);
} catch (err) {
if (!ignore) setError(err.message);
} finally {
if (!ignore) setLoading(false);
}
}
load();
return () => {
ignore = true; // a newer trainNo replaced this request
};
}, [trainNo]);
if (loading) return <p>Loading...</p>;
if (error) return <p role="alert">Could not load the status: {error}</p>;
return <p>{status.name} is running {status.delayMinutes} minutes late</p>;
}You might not need an effect
- Data you can calculate from props or state: calculate it while rendering. No effect, no extra state.
- Something that happens because the user did something (clicked Buy, submitted a form): do it in the event handler, not in an effect.
- Resetting all the state of a component when a prop changes: give the component a key, such as <Profile key={userId} />.
- Loading data in a big app: a framework's data loading or a library usually does it better than a hand-written effect.
// Avoid: extra state and an effect
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
// Better: calculate during render
const fullName = firstName + ' ' + lastName;useRef and custom hooks
import { useRef } from 'react';
function SearchBox() {
const inputRef = useRef(null);
return (
<>
<input ref={inputRef} aria-label="Search" />
<button onClick={() => inputRef.current.focus()}>Search</button>
</>
);
}function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
try {
const saved = localStorage.getItem(key);
return saved !== null ? JSON.parse(saved) : initialValue;
} catch {
return initialValue;
}
});
useEffect(() => {
try {
localStorage.setItem(key, JSON.stringify(value));
} catch {
// storage is full or blocked: keep working without saving
}
}, [key, value]);
return [value, setValue];
}
// In any component:
const [city, setCity] = useLocalStorage('city', 'Pune');Watch the lesson
Effects, data fetching and hooks · 1/7
Rendering should only calculate JSX. Talking to the outside world happens in an effect.
Prefer a video by a teacher? Find free videos on “react useEffect data fetching cleanup useRef custom hooks tutorial” on YouTube ↗
In real life: A live train status page
On a train running status page, you type a train number. An effect with [trainNo] as its dependency fetches the status, shows "Loading..." and then the delay, or a clear error if the network fails. The ignore flag makes sure a slow answer for the old number never replaces the new one. A second effect refreshes the status every 60 seconds with setInterval and clears it in the cleanup, and a useLocalStorage hook remembers your last train number.
Practice (0/3 done)
Write the loading logic an effect would call: async function loadTrainStatus(fetchFn, trainNo). Call fetchFn with the URL "/api/trains/<trainNo>" (for example "/api/trains/12951"). If the response is ok, return { status: "success", data } where data is await response.json(). If response.ok is false, return { status: "error", error: "HTTP <status>" } (for example "HTTP 404"). If fetchFn throws (no network), return { status: "error", error: <the error message> }. The tests pass a fake fetch.
Fix a race condition. weatherEffect(fetchFn, city, setData) is the body of an effect: it starts loading the weather for city and calls setData with the answer. It must return a cleanup function. Right now, if the user switches from Delhi to Mumbai and the Delhi answer arrives last, Delhi's weather overwrites Mumbai's. Add an ignore flag: set it to true in the cleanup, and only call setData when ignore is still false.
In your Vite project, build a "Country info" page that fetches from a free public API you choose (or a local JSON file in the public folder). A select box chooses the item; an effect with that value in its dependency array loads the data with loading and error states and an ignore flag. Add a custom hook useLocalStorage to remember the last choice, and a button that focuses the select with useRef. Watch the effect run twice in development (StrictMode) in the console.
Quick quiz
Read more (free)
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.