Skip to content

When Not to Use React Hooks: The Senior Developer's Guide

Problem

I had a component that needed to track window width. Like a good React developer, I reached for hooks:

WindowWidth.jsx
function WindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handler = () => setWidth(window.innerWidth);
window.addEventListener('resize', handler);
return () => window.removeEventListener('resize', handler);
}, []);
return <div>Window width: {width}px</div>;
}

I thought this was the React way. State, effect, cleanup — all the patterns I’d learned.

Then a senior developer walked by and asked: “Does React need to know about this?”

I paused. “Well, I’m displaying it in the component…”

“But do you need to re-render on every resize? Every pixel change?”

I realized I hadn’t thought about it. I just grabbed for hooks automatically.

The Hook-First Reflex

After months of learning React, I’d developed a reflex:

  • Need to respond to something? useEffect
  • Need to store something? useState
  • Need to share logic? Custom hook

This seemed right. I was “thinking in React.” But I was also creating unnecessary re-renders, memory leaks, and bugs I didn’t understand yet.

My mental model:
Problem exists → Reach for hook → Problem solved
Better mental model:
Problem exists → Ask if React needs to know → Choose appropriate solution

The Question That Changed Everything

That senior developer’s question stuck with me: Does React need to know about this?

I started asking this before every hook. Let me show you what I found.

Question 1: Does React Need to Re-render?

State exists to trigger re-renders. If the value doesn’t affect what’s on screen, you might not need useState.

I tried this experiment with a form:

ControlledInput.jsx
// What I used to do - re-renders on every keystroke
function ControlledInput() {
const [value, setValue] = useState('');
return (
<input
value={value}
onChange={e => setValue(e.target.value)}
/>
);
}

Every character typed triggers a state update, which triggers a re-render. For a simple form that only needs the value on submit, this is overkill.

UncontrolledInput.jsx
// What I do now - no re-renders
function UncontrolledInput() {
const inputRef = useRef(null);
const handleSubmit = () => {
const value = inputRef.current.value;
// Do something with value
};
return <input ref={inputRef} />;
}

The browser handles the typing. React only gets involved when I actually need the value.

Question 2: Can CSS Do This?

I had a hover state that triggered a color change. My first instinct:

HoverState.jsx
// Hook approach - unnecessary re-renders
function HoverableDiv() {
const [isHovered, setIsHovered] = useState(false);
return (
<div
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
style={{ backgroundColor: isHovered ? 'blue' : 'red' }}
>
Hover me
</div>
);
}

Then I remembered: CSS exists.

hover.css
.hoverable {
background-color: red;
}
.hoverable:hover {
background-color: blue;
}

No React. No re-renders. No complexity.

Question 3: Does the Effect Need to Sync with React?

I used useEffect for everything external. Analytics, event listeners, timers — all wrapped in effects.

But sometimes, React doesn’t need to know:

EventListeners.jsx
// Hook approach - React tracks everything
useEffect(() => {
const handler = (e) => {
// Handle custom event
};
window.addEventListener('custom-event', handler);
return () => window.removeEventListener('custom-event', handler);
}, []);
// Plain JS - React doesn't need to know
useEffect(() => {
// This still runs on mount/unmount, but...
}, []);
// Even simpler: if React doesn't need the data, skip the effect
window.addEventListener('custom-event', (e) => {
// Handle directly, no React involvement
});

Common Patterns I Over-Engineered

Pattern 1: Window Resize

I already showed this at the start. Here’s the breakdown:

ResizeOverkill.jsx
// Over-engineered: Re-renders on every pixel change
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handler = () => setWidth(window.innerWidth);
window.addEventListener('resize', handler);
return () => window.removeEventListener('resize', handler);
}, []);

When is this actually needed? Only if:

  • The width affects rendering
  • Other components need to sync with this value

If I just need to check width occasionally:

ResizeOnDemand.jsx
// Simpler: Check when needed
const checkWidth = () => window.innerWidth;
// Or use CSS media queries for responsive styles
@media (max-width: 768px) {
/* styles */
}

Pattern 2: Animation State

I had a component that animated on mount:

AnimationState.jsx
// Over-engineered: React manages animation timing
function AnimatedCard() {
const [isAnimating, setIsAnimating] = useState(false);
useEffect(() => {
setIsAnimating(true);
const timer = setTimeout(() => setIsAnimating(false), 1000);
return () => clearTimeout(timer);
}, []);
return <div className={isAnimating ? 'animate' : ''} />;
}

CSS can handle this:

animation.css
.card {
animation: fadeIn 1s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}

No state. No effect. No cleanup. The browser’s animation engine handles everything.

Pattern 3: Local Storage Sync

I wanted to persist a value to localStorage. My first attempt:

StorageSync.jsx
// Over-engineered: Sync on every change
const [theme, setTheme] = useState(() => {
return localStorage.getItem('theme') || 'light';
});
useEffect(() => {
localStorage.setItem('theme', theme);
}, [theme]);

This works, but if the component never needs to re-render based on the stored value, why maintain state?

StorageDirect.jsx
// Simpler: Read on demand
const getTheme = () => localStorage.getItem('theme') || 'light';
const setTheme = (theme) => localStorage.setItem('theme', theme);

The state version makes sense if other components need to react to theme changes. If not, direct access is cleaner.

When Hooks ARE the Right Choice

After all this, I want to be clear: hooks are essential. I’m not saying avoid them. I’m saying use them deliberately.

Hooks are appropriate when:

  1. React needs to re-render based on the value

    ValidState.jsx
    const [count, setCount] = useState(0);
    // Count affects rendering, state makes sense
  2. Multiple components need to share state

    SharedState.jsx
    // Context + hooks for shared state across components
    const [user, setUser] = useState(null);
  3. You need to sync with external systems AND React

    ValidSync.jsx
    // React needs to know about subscription status
    useEffect(() => {
    const subscription = props.source.subscribe();
    return () => subscription.unsubscribe();
    }, [props.source]);
  4. The component’s output depends on derived data

    DerivedData.jsx
    const [items, setItems] = useState([]);
    const sortedItems = useMemo(() => {
    return items.sort((a, b) => a.name.localeCompare(b.name));
    }, [items]);

The key question remains: Does React need to know?

The Senior Mindset

That senior developer who asked me the question? I now understand what they meant.

Juniors think in tools: “I need a hook for this.” Seniors think in problems: “What am I trying to solve? Does React need to be involved?”

This shift in thinking changes everything:

Junior approach:
Problem → What hook solves this? → Implement hook
Senior approach:
Problem → What's the simplest solution? → Does React need to know?
→ If yes: Which hook? (if any)
→ If no: Plain JavaScript / CSS / nothing

From discussions I’ve read, this is what experienced developers actually do:

“Seniors know the best React hook is no hook at all because you realized that you could just register an event listener or write a regular JavaScript function to solve the problem in a simpler way.”

That’s not about knowing more hooks. It’s about knowing when you don’t need them.

What I Changed

Before Reaching for a Hook

I now run through this checklist:

  1. Does this affect rendering? If no, consider alternatives.
  2. Will this cause unnecessary re-renders? If yes, is there a simpler way?
  3. Can CSS or plain JS solve this? Often yes.
  4. Is the complexity worth it? Hooks add cognitive overhead and potential bugs.

Code Review Questions

When reviewing my own code or others’, I ask:

  • Why is this state here?
  • What breaks if we remove this effect?
  • Is React the right tool for this job?

Default to Simplicity

I now start with the simplest solution:

1. Can CSS do this? Use CSS.
2. Can plain JavaScript do this? Use JavaScript.
3. Does React need to know? Use hooks.

Summary

In this post, I explored when React hooks are overkill. The key point is to ask whether React needs to manage the state before reaching for hooks. Many problems can be solved with plain event listeners, regular JavaScript functions, or CSS — avoiding unnecessary re-renders, complexity, and bugs.

The best React code is often the code you didn’t have to write. Before using a hook, pause and ask: “Does React need to know about this?”

Many “React problems” are actually just JavaScript problems.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments