3 · Connect, test and build · 18 MIN
Use an effect only for an external system
Effects synchronize with things outside React, and may need cleanup.
The clock is driven by a browser timer rather than a click. useEffect creates that timer after a render is committed, then returns a cleanup function that cancels it. The empty dependency array means the effect does not depend on changing props or state; the functional setter reads the latest count. Development Strict Mode may start, clean up and restart the effect to expose missing cleanup. Filtering an array does not need this mechanism: calculate derived values while rendering.
Identify setInterval as the external browser API. Observe the returned function runs when the component leaves. Toggle the child and distinguish hiding it from merely hiding CSS. Explain the extra development setup/cleanup cycle.
Before you start
Install Node.js 22.12+ (or a supported newer LTS). In a terminal run: npm create vite@latest codingneed-react -- --template react. Then cd codingneed-react, npm install, and npm run dev. Open the local URL printed by Vite. Replace src/App.jsx with ONE lesson example at a time. JSX renders a browser interface; it is not a plain console script.
File for this example: App.jsx
New words, explained
- effect
- Synchronization after React commits a render.
- cleanup
- Undoing a subscription, timer or other resource.
- unmount
- Removing a component and its associated state.
Follow the example step by step
- Identify setInterval as the external browser API.
- Observe the returned function runs when the component leaves.
- Toggle the child and distinguish hiding it from merely hiding CSS.
- Explain the extra development setup/cleanup cycle.
You are ready to move on when: Hidden timers are cleaned up. Showing a timer resets its component state. Explain why this needs an effect but list filtering does not.
Read the example
import {useEffect, useState} from 'react';
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const timer = setInterval(() => setSeconds(value => value + 1), 1000);
return () => clearInterval(timer);
}, []);
return <p>Study time: {seconds} seconds</p>;
}
export default function App() {
const [show, setShow] = useState(true);
return <main><h1>Study timer</h1><button onClick={() => setShow(value => !value)}>Toggle timer</button>{show && <Timer />}</main>;
}Check the expected output
The timer increments about once a second. Hiding it cancels its interval. Showing it mounts a fresh timer at zero.
Your challenge
Add a console message in setup and cleanup to observe their order. Toggle the timer five times and confirm it does not tick faster.
Solution cost: Discuss the operations in this small example; rendering and I/O costs depend on the host. time · Proportional to the example’s retained data. space
Common trap
An empty dependency array is not permission to ignore values that the effect reads.
Study the project implementation
import {useEffect, useState} from 'react';
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const timer = setInterval(() => setSeconds(value => value + 1), 1000);
return () => clearInterval(timer);
}, []);
return <p>Study time: {seconds} seconds</p>;
}
export default function App() {
const [show, setShow] = useState(true);
return <main><h1>Study timer</h1><button onClick={() => setShow(value => !value)}>Toggle timer</button>{show && <Timer />}</main>;
}Further reading: Official documentation
Next lesson: Project · Build a filterable reading list →