Written by: ekwoster.dev on Fri Oct 03

πŸ”₯ Stop Using useState for Everything in React β€” The Hidden Performance Trap No One Talks About!

πŸ”₯ Stop Using useState for Everything in React β€” The Hidden Performance Trap No One Talks About!

Cover image for πŸ”₯ Stop Using useState for Everything in React β€” The Hidden Performance Trap No One Talks About!

πŸ”₯ Stop Using useState for Everything in React β€” The Hidden Performance Trap No One Talks About!

If you’ve been developing in React for a while, you’ve probably used useState a lot... maybe too much. While useState is a powerful and fundamental React hook, overusing it β€” or using it for the wrong purposes β€” can actually degrade your app's performance and cause unnecessary re-renders.

In this deep dive, we’ll explore better alternatives, expose hidden pitfalls of useState, and walk through code examples that prove why replacing it in the right scenarios is not only possible, but highly recommended.

⚠️ TL;DR: Stop using useState for global/scoped state that never needs component re-renders. Discover useRef, useReducer, and external state managers.


🚨 Problem: Reactivity vs. Responsibility

React is reactive. When a component’s state changes, it re-renders. This is by design. But what if you have state that doesn’t need to trigger re-renders? Like:

  • Tracking animation steps
  • Caching timers, intervals, or DOM elements
  • Logging values
  • Immutable values

If you’re storing data in useState, React assumes:

β€œOh, they probably want to re-render this component when the value changes.”

This leads to unintentional waste. πŸ˜΅β€πŸ’«


Better Alternatives: The Trio You Should Be Using

1. useRef: Your Secret Cache πŸ’Ύ

When you want to store a value without causing re-renders, useRef is gold.

❌ Bad: Using useState

const [timerId, setTimerId] = useState(null);

useEffect(() => { const id = setInterval(() => console.log('tick'), 1000); setTimerId(id); return () => clearInterval(id); }, []);

πŸ”΄ Every setTimerId call triggers a re-render that brings no UI benefit.

βœ… Good: Using useRef

const timerIdRef = useRef();

useEffect(() => { timerIdRef.current = setInterval(() => console.log('tick'), 1000); return () => clearInterval(timerIdRef.current); }, []);

βœ… Cleaner. No unnecessary re-renders. Use useRef for persistent, mutable data that doesn’t need to trigger views.


2. useReducer: Scalable State Management βš™οΈ

If your component state is growing in complexity, stop juggling multiple useStates.

❌ Messy State Explosion

const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const [email, setEmail] = useState('');

βœ… Centralize with useReducer

const initialState = { firstName: '', lastName: '', email: '' };

function reducer(state, action) { return { ...state, [action.field]: action.value, }; }

const [state, dispatch] = useReducer(reducer, initialState);

<input value={state.firstName} onChange={e => dispatch({ field: 'firstName', value: e.target.value })} />

🧠 useReducer allows predictability and better debugging especially as your form or logic grows.


3. External State (Zustand, Jotai, Recoil) 🌐

For global or cross-component state, like user auth or theme status:

βœ… Use external lightweight stores like Zustand:

npm install zustand

// store.js import create from 'zustand';

const useStore = create(set => ({ theme: 'light', toggleTheme: () => set(state => ({ theme: state.theme === 'light' ? 'dark' : 'light' })) }));

// App.js const theme = useStore(state => state.theme); const toggleTheme = useStore(state => state.toggleTheme);

Zustand doesn't rely on React’s render cycle. It offers selective reactivity + blazing-fast updates.


Bonus Tip: Memoization Magic πŸͺ„

Avoid passing fresh function or object references every render unless they’re memoized.

// Wrong - triggers child re-render <MyComponent onClick={() => doSomething()} />

// Right const memoizedHandler = useCallback(() => doSomething(), []); <MyComponent onClick={memoizedHandler} />


Final Verdict: Know the Tools, Beat the Re-Renders 🧠

React is not about forcibly using useState and useEffect everywhere. We have better tools now.

CaseBetter Alternative
Persisting mutable datauseRef
Complex or grouped state updatesuseReducer
Shared/global stateZustand, Jotai, Redux
One-off logic or flagsuseRef / local vars

πŸš€ TL;DR Checklist β€” Stop Overusing useState

  • βœ… Need to persist but not re-render? Use useRef
  • βœ… Logical groups of state? Use useReducer
  • βœ… Global/shared state? Use external stores
  • βœ… Avoid useState for tracking things like timeouts, indexes, canvas, etc.

Level-up your React apps πŸš€ by using the right state tool for the job.

🧡 Don't forget to share this article with that one dev who still uses 15 useStates in a form! πŸ‘€


What's Next?

Want a follow-up article with practical migration steps from useState to useReducer or Zustand? Drop a comment below or DM me on X (@reactDoctor)!


πŸ‘‰ If you need help building efficient user interfaces or state-driven apps – we offer frontend development services.