When should you use useEffect, and when should you avoid it?
Use useEffect to sync your component with something outside React, such as a network request, a subscription or a browser API. Avoid it for work you can do during rendering or inside an event handler.
Explain it simply
Rendering in React should be like a recipe: same ingredients (props and state) always give the same dish (JSX). An effect is for things that are not part of the dish, such as ringing the doorbell (talking to a server), opening a window (a browser API) or subscribing to a newsletter (a WebSocket). useEffect says: "after you show this on screen, go and sync with the outside world".
If something can be worked out from props or state while rendering, it does not need an effect. If something happens because the user clicked a button, it belongs in the click handler.
Example
function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([]);
// Good use: keep a connection in sync with roomId
useEffect(() => {
const connection = connect(roomId);
connection.on("message", (m) => setMessages((list) => [...list, m]));
return () => connection.disconnect(); // clean up when roomId changes
}, [roomId]);
// No effect needed: derive during render
const unread = messages.filter((m) => !m.read).length;
return <p>{unread} unread</p>;
}How it works
React runs the effect after the screen updates. When a value in the dependency array changes, React first runs the cleanup function from the previous effect and then runs the effect again. In development, Strict Mode runs effects twice on mount to help you catch missing cleanups.
Real-world use
- Fetching data when an id changes, with a cleanup that ignores stale responses.
- Subscribing to window resize, online/offline status or a WebSocket.
- Integrating non-React widgets like maps or charts.
Common mistakes
- Copying props into state with an effect ("derived state"). Calculate it during render instead, or use
useMemoif it is expensive. - Handling user actions in an effect by watching a flag. Put that logic in the event handler.
- Forgetting cleanup, which leaves duplicate subscriptions or timers.
- Leaving out dependencies to "stop loops". Fix the logic instead; missing dependencies cause stale values.
What to say in the interview
"useEffect is for synchronising a component with an external system, like a subscription, a timer or a network request, and it should return a cleanup. I avoid effects for derived data, which I compute during render, and for user actions, which go in event handlers. I keep the dependency array honest, and if an effect runs too often I restructure the code instead of hiding dependencies."
Follow-up questions
- Why does my effect run twice in development?
- How do you avoid race conditions when fetching in an effect?
- What is the difference between useEffect and useLayoutEffect?