1. Deep Dive: The Lifecycle of State
1.1 State is a Snapshot
This is the most common point of confusion. Setting state does not change the variable immediately. It schedules a re-render with the new value.
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1); // Schedules update to 1
setCount(count + 1); // Schedules update to 1 (still uses old 'count' value of 0)
setCount(count + 1); // Schedules update to 1
console.log(count); // Prints 0!
}
To fix this, use the updater function: setCount(prev => prev + 1).
1.2 The Dependency Array
useEffect is not a lifecycle method like componentDidMount. It is a synchronization mechanism. "Synchronize this code with these variables."
[]: "I depend on nothing. Run once."[userId]: "I depend on userId. Run when it changes."- No array: "I depend on everything. Run every render." (Bad performance).
1.3 Cleanup Functions
If your effect subscribes to something (like a WebSocket or a Timer), you MUST return a cleanup function. React runs this function before the component unmounts or before the effect runs again.
useEffect(() => {
const timer = setInterval(tick, 1000);
return () => clearInterval(timer); // The Cleanup
}, []);
1.4 Industry Standard: Custom Hooks
Senior developers rarely write complex useEffect logic directly inside UI components. They extract it into Custom Hooks. This separates logic from view.
// Instead of writing useEffect in every component:
function UserProfile({ id }) {
const { data, loading, error } = useFetch(`/users/${id}`);
// ... render logic
}
📚 Official Documentation References
- useState
- useEffect
- State as a Snapshot (Crucial!)
Build "SmartCounter" with History
As a user, I want to see a history of my counter values so I can track my changes over time.
- Create a
SmartCountercomponent. - State: Track
count(number) andhistory(array of numbers). - Actions: Add Increment (+1) and Decrement (-1) buttons.
- Logic: Whenever
countchanges, append the new value tohistory. - Display: Show the current count and a list of previous values.
- Auto-Increment: Add a "Start/Stop" button that increments the counter every second using
setInterval. - Cleanup: Ensure the interval is cleared when the component unmounts or when "Stop" is clicked.
- Bug Prevention: Ensure the interval doesn't speed up if "Start" is clicked multiple times.
User Story: As a developer, I want a reusable hook to track the window dimensions so I can make responsive logic in JS.
Acceptance Criteria:
- Create `useWindowSize.ts`.
- Return `{ width, height }`.
- Add event listener for `resize`.
- Crucial: Clean up the event listener on unmount to prevent memory leaks.
2. The Setup Challenge
🐛 Scenario: The Infinite Loop
The Setup:
function BadComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1);
}); // Missing dependency array!
return <div>{count}</div>;
}
The Bug: This component crashes the browser tab. Why?
The Task:
- Explain the cycle: Render -> Effect -> SetState -> Render...
- Fix it so it only runs once on mount.
- Challenge: How would you make it run only when a prop
userIdchanges?