useEffect vs Event Handler
useEffect vs Event Handler
A very common mistake in modern React is using the useEffect hook to execute logic that should actually be placed inside an Event Handler.
Interviewers often present a buggy block of code and ask you to identify why it’s a bad pattern and how to fix it.
The Concept
- Event Handlers run in response to a specific user interaction (e.g., clicking a button, submitting a form, typing in an input).
- Effects (
useEffect) run in response to a render (the component appearing on screen, or a specific piece of state/prop changing).
The Anti-Pattern: Triggering Effects from Interactions
Imagine an e-commerce checkout page. When the user clicks “Buy”, we want to submit the order to the API and show a notification.
❌ The Buggy Approach (Using useEffect)
function Checkout() {
const [isBuying, setIsBuying] = useState(false);
// BAD: Using an Effect to handle a user action
useEffect(() => {
if (isBuying) {
api.submitOrder();
showNotification('Order placed!');
setIsBuying(false); // Reset the trigger
}
}, [isBuying]);
return <button onClick={() => setIsBuying(true)}>Buy</button>;
}
Why this is terrible:
- Unpredictable Execution: If
isBuyingsomehow gets set totrueby another part of the application, or if the component re-mounts while the state is preserved (e.g., during Fast Refresh or React 18 Strict Mode), the order will be submitted twice accidentally! - Indirection: The code is hard to read. You see the button click, but you have to jump to a completely different part of the file (the
useEffect) to figure out what that click actually does. - Unnecessary Renders: Setting
isBuyingto true triggers a render, the effect runs, sets it back to false, which triggers another render.
The Solution: Use Event Handlers
If the logic needs to run because the user did something, the code belongs directly inside the onClick or onSubmit event handler.
✅ The Correct Approach (Using Event Handlers)
function Checkout() {
// GOOD: The logic is directly tied to the user's action
const handleBuyClick = () => {
api.submitOrder();
showNotification('Order placed!');
};
return <button onClick={handleBuyClick}>Buy</button>;
}
When SHOULD you use useEffect?
You should only use useEffect when the logic needs to run because the component exists or a specific value changed, regardless of what the user did.
Examples of good Effects:
- Fetching initial data when a page loads (e.g.,
useEffect(() => { api.fetchUserProfile() }, [])). - Synchronizing React state with a non-React system (e.g., attaching a map library to a DOM node, or setting up a WebSocket connection).
- Updating the document title when a specific state changes (e.g.,
useEffect(() => { document.title = `You have ${unreadCount} messages` }, [unreadCount])).
Interview Questions
Q: Should you trigger a POST request to save a form inside a useEffect or an onClick event handler?
A: In the onClick event handler! Network requests triggered by direct user actions (like clicking submit) should be handled immediately in the event handler. useEffect is strictly for synchronizing the component with external systems when it mounts or updates.