Hot Take

Why useEffect Is the Only React Hook You'll Ever Need

After 8 years of professional React development, I've come to a controversial conclusion: we've been overcomplicating everything. The answer was always right in front of us.

Deyan Dobrinov
Deyan Dobrinov April 1, 2026 · 12 min read

Let me start with a bold claim: every React problem you've ever encountered can be solved with useEffect. State management? useEffect. Data fetching? useEffect. Form validation? useEffect. Routing? Believe it or not, useEffect.

I know what you're thinking. "But the React docs say—" Stop right there. The React docs also said class components were the future. The docs are a guide, not gospel. What I'm about to share with you is the result of years of battle-tested production experience, thousands of pull requests, and one very long shower thought.

Key Insight

If React didn't want us to use useEffect everywhere, why did they make it work with literally everything? Think about it.

The Numbers Don't Lie

Before we dive in, let me share some data from my team's migration to what I call Effect-Driven Development (EDD):

97%
Code now uses useEffect
Re-renders per second
0
Developers who complained (that still work here)

The Universal Effect Pattern

Most developers use useEffect timidly — a little data fetching here, a DOM subscription there. They're missing the bigger picture. useEffect is not just a hook. It's an architecture.

Here's the core principle: every piece of logic in your application is, fundamentally, a side effect of something. Your component rendered? Side effect. User clicked a button? Side effect. The universe exists? Believe it or not — side effect. Once you internalize this, useEffect becomes the only tool you need.

1. State Management with useEffect

Why use useReducer, Redux, Zustand, or Jotai when useEffect can synchronize all your state perfectly? The trick is to let effects cascade naturally:

JSXfunction ShoppingCart() {
  const [items, setItems] = useState([]);
  const [total, setTotal] = useState(0);
  const [tax, setTax] = useState(0);
  const [grandTotal, setGrandTotal] = useState(0);
  const [isExpensive, setIsExpensive] = useState(false);
  const [showWarning, setShowWarning] = useState(false);

  // Effect cascade — each one triggers the next!
  useEffect(() => {
    setTotal(items.reduce((sum, i) => sum + i.price, 0));
  }, [items]);

  useEffect(() => {
    setTax(total * 0.2);
  }, [total]);

  useEffect(() => {
    setGrandTotal(total + tax);
  }, [total, tax]);

  useEffect(() => {
    setIsExpensive(grandTotal > 100);
  }, [grandTotal]);

  useEffect(() => {
    setShowWarning(isExpensive);
  }, [isExpensive]);

  // Beautiful. 5 effects. 6 re-renders. Peak React.
}

Some people call this an "effect cascade." I call it Reactive Elegance. Each piece of state feeds into the next like a symphony. Sure, the component re-renders 6 times per item change, but modern hardware is fast. Are we really going to let a few hundred unnecessary re-renders get in the way of beautiful code?

2. Data Fetching — The Effect Way

React Query? SWR? use()? Overengineered abstractions for a solved problem. All you need is useEffect and a dream:

JSXfunction UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [retryCount, setRetryCount] = useState(0);

  // Fetch the user
  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then(r => r.json())
      .then(setUser)
      .catch(setError)
      .finally(() => setLoading(false));
  }, [userId, retryCount]);

  // Auto-retry on error (genius)
  useEffect(() => {
    if (error) {
      const timer = setTimeout(() => {
        setRetryCount(c => c + 1);
      }, 1000);
      return () => clearTimeout(timer);
    }
  }, [error]);

  // Log every state change (for observability)
  useEffect(() => {
    console.log('State updated:', { user, loading, error, retryCount });
  }, [user, loading, error, retryCount]);

  // Who needs React Query when you have THIS?
}

"But what about race conditions when userId changes?" Look, if your users are clicking that fast, that's a UX problem, not a code problem. Set the onClick handler to disabled — inside a useEffect, of course.

3. Form Validation — Effect-Powered

Libraries like Formik and React Hook Form are just abstractions over what is, ultimately, a series of useEffect calls. Why add a dependency when the language of effects is already at your fingertips?

JSXfunction SignupForm() {
  const [email, setEmail] = useState('');
  const [emailTouched, setEmailTouched] = useState(false);
  const [emailValid, setEmailValid] = useState(false);
  const [emailError, setEmailError] = useState('');
  const [emailChecking, setEmailChecking] = useState(false);
  const [password, setPassword] = useState('');
  const [passwordStrength, setPasswordStrength] = useState(0);
  const [formValid, setFormValid] = useState(false);
  const [submitAttempted, setSubmitAttempted] = useState(false);

  useEffect(() => { setEmailValid(email.includes('@')); }, [email]);
  useEffect(() => { setEmailError(emailTouched && !emailValid ? 'Bad email' : ''); }, [emailTouched, emailValid]);
  useEffect(() => { setPasswordStrength(password.length > 12 ? 3 : password.length > 8 ? 2 : 1); }, [password]);
  useEffect(() => { setFormValid(emailValid && passwordStrength >= 2); }, [emailValid, passwordStrength]);

  // Check email availability on the server
  useEffect(() => {
    if (emailValid) {
      setEmailChecking(true);
      fetch(`/api/check-email?e=${email}`)
        .then(r => r.json())
        .then(d => { if (d.taken) setEmailError('Taken!'); })
        .finally(() => setEmailChecking(false));
    }
  }, [emailValid, email]);

  // API call on EVERY KEYSTROKE. Real-time validation. You're welcome.
}

Nine state variables. Five effects. An API call on every keystroke. Some people might call this "over-engineered." I call it thorough.

useEffect vs. The Competition

Let's objectively compare useEffect to the alternatives the React community keeps pushing:

Feature useEffect Others
Can do literally anything
Built into React
Triggers re-renders (as many as you want!) Sometimes
Makes code reviewers nervous
Creates job security (nobody else can debug it)
Supported by Dan Abramov He wrote a blog post about it once Unclear

Advanced Patterns: The Effect Singularity

Once you've mastered basic Effect-Driven Development, you're ready for the advanced technique I call The Effect Singularity — a single component where every line of logic lives inside a useEffect.

JSXfunction App() {
  const [route, setRoute] = useState(window.location.pathname);
  const [theme, setTheme] = useState('dark');
  const [user, setUser] = useState(null);
  const [notifications, setNotifications] = useState([]);

  // Router (who needs react-router?)
  useEffect(() => {
    const handler = () => setRoute(window.location.pathname);
    window.addEventListener('popstate', handler);
    return () => window.removeEventListener('popstate', handler);
  }, []);

  // Auth
  useEffect(() => {
    fetch('/api/me').then(r => r.json()).then(setUser);
  }, []);

  // Theme sync to DOM
  useEffect(() => {
    document.body.className = theme;
  }, [theme]);

  // Notifications polling
  useEffect(() => {
    const id = setInterval(() => {
      fetch('/api/notifications')
        .then(r => r.json())
        .then(setNotifications);
    }, 3000);
    return () => clearInterval(id);
  }, []);

  // Analytics
  useEffect(() => {
    fetch('/api/analytics', {
      method: 'POST',
      body: JSON.stringify({ route, user: user?.id })
    });
  }, [route, user]);

  // Tab title
  useEffect(() => {
    document.title = `My App - ${route} (${notifications.length} new)`;
  }, [route, notifications]);

  // 7 effects and counting. This is the way.
}
Responding to Critics

Some people will tell you that useEffect is "the wrong tool for derived state" or that "you don't need an effect for that." These people are afraid of power. When the React team wrote You Might Not Need an Effect, they were clearly writing satire.

The Effect-Driven Architecture (EDA)

I've formalized my approach into a proper architecture. Here are the core principles:

  1. Every derived value gets its own useState + useEffect. Never compute anything inline. What are we, savages?
  2. Never use useMemo or useCallback. These are crutches for people who don't understand the render cycle. If your component renders 47 times, it's because React wants it to.
  3. Event handlers should only set state. The actual logic goes in — you guessed it — a useEffect that watches that state.
  4. If the linter warns you about a missing dependency, add everything it suggests. The linter knows best. If this creates an infinite loop, that's a feature — your component is now always up to date.
  5. Cleanup functions are optional. Memory leaks are just your app being eager.

Real-World Testimonials

Don't just take my word for it. Here's what other developers have said after adopting EDD:

"Our bundle size didn't change at all, because useEffect is built in. Literally zero new dependencies. My manager loved it."
— Anonymous Senior Developer

"I replaced our entire Redux store with 43 useEffects. The Redux DevTools extension stopped working, but honestly that's a feature — now nobody can spy on our state."
— Staff Engineer who asked not to be named

"After adopting Effect-Driven Development, our React Profiler shows what I can only describe as 'a waterfall of purpose.' Each re-render is a testament to reactivity."
— Performance Engineer (former)

Conclusion: Embrace the Effect

We spend so much time fighting useEffect — wrapping it in custom hooks, replacing it with libraries, trying to avoid it — that we never stop to ask: what if we just… used it more?

useEffect is React's most honest hook. It doesn't pretend to be something it's not. It says: "Give me a function, give me some deps, and I'll run it. Maybe twice in dev mode. Maybe in a closure that captured stale state. But I'll run it."

And isn't that all we can really ask of our tools?

Start small. Convert one useMemo to a useState + useEffect. Feel the power. Then convert another. Before you know it, you'll be living the Effect-Driven lifestyle, and you'll never look back.


This is an April 1st post. Please don't actually do any of this. No production apps were harmed in the making of this post. Probably.


More Posts