What they're testing
Whether you separate "derive from what you have" from "synchronise with the outside world".
The short answer~30 seconds
useEffect synchronises with things outside React: DOM event subscriptions, WebSocket connections, third-party APIs, the document title. If you can COMPUTE a value during render, don't use an effect — compute it. An effect that only sets state from props causes an extra render pass, and under React 18 StrictMode it runs twice in development, surfacing bugs in confusing ways.
The long answer
The most common wrong pattern is derived state: you have items and query, then an effect that filters items into filtered. It costs two renders per keystroke, opens a window where filtered disagrees with items, and adds a second source of truth that can drift. Just write const filtered = items.filter(…) in the component body; if the filter is genuinely expensive, wrap it in useMemo — but only after measuring.
The second wrong pattern is reacting to user events through an effect: a click sets a submitted state, and an effect notices it changed and fires the request. That turns one clear action into a causal chain you have to read backwards. Event logic belongs in the event handler — simpler, more readable, and independent of the render cycle.
On StrictMode's double invocation: it isn't a bug and doesn't happen in production. It deliberately mounts, unmounts and mounts again to expose effects missing cleanup. If running twice breaks your effect, you're almost certainly missing a cleanup function — and the same problem will occur in production whenever the component remounts for any other reason.
// Không cần effect: tính thẳng khi render
const filtered = items.filter((i) => i.name.includes(query));
// Cần effect: đồng bộ với thứ ngoài React, và có dọn dẹp
useEffect(() => {
const socket = new WebSocket(url);
socket.onmessage = onMessage;
return () => socket.close(); // <- StrictMode phơi bày nếu thiếu
}, [url]);What they'll ask next
?What's wrong with fetching in useEffect?
It works, but you end up reimplementing: races when parameters change quickly, cancellation, caching, retries, loading states. Those are precisely what TanStack Query or a framework loader already solves. In the Next.js App Router the best answer is fetching in a Server Component with no effect at all.
?How is useLayoutEffect different?
It runs SYNCHRONOUSLY after the DOM updates but BEFORE the browser paints, so it's for measuring and repositioning without a visible flash. In exchange it blocks painting, so heavy work there is a reliable way to make INP worse.
These lose points
- Using an effect to keep two pieces of state in sync. It's a sign one of them shouldn't be state.
- Disabling StrictMode because effects run twice. You've turned off the thing telling you there's a bug.