When I first started working with React Hooks, useEffect felt like this magical black box. You toss in some logic, slap on a dependency array, and just hope everything works.

Fast forward 3.5 years, and I’ve learned that truly mastering useEffect can make or break how clean, performant, and bug-free your components are.

In this post, I’ll walk you through what useEffect actually does, how to use it right, common pitfalls, and some patterns I personally use (and avoid) in production apps.


🧠 What is useEffect?

At its core, useEffect is how React lets you synchronize a component with external systems — network requests, subscriptions, DOM mutations, timers, and more.

Think of it as:

“Run this code after the render.”

The basic signature:

useEffect(() => {
  // side effect logic here

  return () => {
    // cleanup logic here (optional)
  };
}, [dependencies]);

The useEffect hook runs after the DOM has been painted. That’s important because unlike class components where componentDidMount and componentDidUpdate are separate, useEffect handles both — and more — depending on how you configure the dependency array.


🔄 Dependency Array Demystified

The second argument is where the real magic happens:

Dependency Array What Happens
[] Runs only once (after initial render)
[a, b] Runs on initial render + whenever a or b change
(no array) Runs on every render
// Example: Fetch on mount
useEffect(() => {
  fetchUser();
}, []);

☝️ Always be intentional with dependencies — it affects when your effect runs.


✅ Common Use Cases

Here are a few practical examples I use regularly:


1. 📡 Fetching data on mount

useEffect(() => {
  async function fetchData() {
    const res = await fetch('/api/data');
    const json = await res.json();
    setData(json);
  }

  fetchData();
}, []);

2. 🖱️ Subscribing to events

useEffect(() => {
  const handleResize = () => setWidth(window.innerWidth);
  window.addEventListener('resize', handleResize);

  return () => window.removeEventListener('resize', handleResize);
}, []);

3. 🧮 Re-running logic based on props/state

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]);

⚠️ Common Pitfalls (I’ve Been There)


❌ Missing dependencies

React doesn’t “guess” what your effect depends on. If you use a variable in your effect but don’t include it in the dependency array, you’ll likely run into bugs — especially async or delayed ones.

Enable ESLint’s react-hooks/exhaustive-deps rule

It’s a life-saver. Seriously.


❌ Doing too much in one useEffect

// This is not ideal
useEffect(() => {
  fetchData();
  setupSubscription();
  updateLocalStorage();
}, []);

This is a code smell. Each effect should have a single responsibility. Splitting them into multiple useEffect hooks keeps dependencies accurate and effects easier to debug.


💡 Best Practices

  • 🔹 Keep effects focused: One responsibility per hook
  • 🧹 Always clean up: Especially for timers, listeners, subscriptions
  • 🧠 Use stable references: For objects/functions using useRef or useCallback
  • 📏 Don’t define unnecessary functions inside useEffect unless they’re only needed there
  • 🧰 Use custom hooks to extract and reuse common effects cleanly

⚡ Real-World Tip: useEffect ≠ Lifecycle Methods

A mistake I made early on was trying to map lifecycle methods 1:1 with hooks — thinking useEffect == componentDidMount or componentDidUpdate.

But that’s not how hooks work.

Instead, ask yourself:

“What side effect should run in reaction to this state or prop?”

This mental model leads to cleaner, more predictable code than trying to mimic class lifecycles.


🧪 Bonus: When Not to Use useEffect

Sometimes you don’t need useEffect at all. Some common cases:

  • Derived state: Compute values directly in render or via useMemo
  • DOM reads/writes before paint: Use useLayoutEffect instead
  • Transforming props/state: Often better handled directly in JSX or helpers
// ❌ Don't do this
useEffect(() => {
  setX(props.value * 2);
}, [props.value]);

// ✅ Do this instead
const x = props.value * 2;

✨ Final Thoughts

useEffect is powerful — but only when used with care.

Once you stop thinking of it as a lifecycle substitute and start thinking of it as a reaction engine — something that runs when certain values change — things start to click.

So next time you reach for useEffect, pause and ask:

  • 🔍 Am I tracking the correct dependencies?
  • ✂️ Can I break this into smaller effects?
  • ❓ Do I even need an effect?

Thanks for reading! 🙌

If you found this helpful or have your own useEffect war stories, I’d love to hear them. Drop a comment or share how you approach effects in your React apps!