DEV Community

Cover image for Best Practices for Handling Side Effects in React
Nilupul Perera
Nilupul Perera

Posted on

Best Practices for Handling Side Effects in React

When working with React, handling side effects correctly can make or break your component’s performance, readability, and predictability. Side effects—like data fetching, subscriptions, or DOM manipulations—shouldn’t be sprinkled all over your codebase like confetti.

Here’s a deep dive into the best practices every serious React dev should follow when dealing with side effects.


⚠️ First, What Are Side Effects in React?

Side effects are operations that affect something outside the scope of the current function being executed. In React, they can include:

  • Fetching data from APIs
  • Manually modifying the DOM
  • Subscribing to WebSocket or event listeners
  • Setting timeouts or intervals
  • Logging or analytics tracking

These are not part of the component’s render output and must be handled outside the render phase.


✅ 1. Use useEffect for Declarative Side Effects

React's useEffect hook is the official home for most side effects in functional components.

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

  fetchData();
}, []);
Enter fullscreen mode Exit fullscreen mode

Best practice:
Use the dependency array correctly. It should contain everything your effect depends on.


✅ 2. Separate Concerns — Don't Cram Everything Into One Effect

Avoid the “God effect” trap where one useEffect tries to do it all. Split effects by purpose: data fetching, subscriptions, cleanup logic, etc.

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

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

useEffect(() => {
  // Fetch data separately
}, []);
Enter fullscreen mode Exit fullscreen mode

✅ 3. Always Clean Up Subscriptions and Timeouts

If you don’t clean up, you’ll leak memory and trigger bugs during unmounting or re-renders.

useEffect(() => {
  const timer = setTimeout(() => {
    console.log("Delayed log");
  }, 1000);

  return () => clearTimeout(timer);
}, []);
Enter fullscreen mode Exit fullscreen mode

✅ 4. Avoid Side Effects During Rendering

Never call fetch(), setTimeout(), or manipulate the DOM directly during render.

🚫 Bad:

const MyComponent = () => {
  fetch("/api/data"); // NOPE
  return <div>Hello</div>;
};
Enter fullscreen mode Exit fullscreen mode

✅ Good:

useEffect(() => {
  fetch("/api/data");
}, []);
Enter fullscreen mode Exit fullscreen mode

✅ 5. Use Custom Hooks to Encapsulate Reusable Side Effects

If you're repeating effect logic, it’s time to extract it into a custom hook.

function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener("resize", handleResize);
    return () => window.removeEventListener("resize", handleResize);
  }, []);

  return width;
}
Enter fullscreen mode Exit fullscreen mode

Then use it like:

const width = useWindowWidth();
Enter fullscreen mode Exit fullscreen mode

✅ 6. Be Mindful of Async Functions in useEffect

You can't pass an async function directly to useEffect. Instead, define an async function inside it and call it.

useEffect(() => {
  const loadData = async () => {
    try {
      const res = await fetch("/api/data");
      const json = await res.json();
      setData(json);
    } catch (err) {
      console.error(err);
    }
  };

  loadData();
}, []);
Enter fullscreen mode Exit fullscreen mode

✅ 7. Don’t Use Effects for State Derivation

If your state can be derived from props or other state, do it in the render—not with side effects.

// ✅ Good
const derivedState = someValue > 0;

// 🚫 Bad
useEffect(() => {
  setDerivedState(someValue > 0);
}, [someValue]);
Enter fullscreen mode Exit fullscreen mode

🧠 Final Thoughts

Side effects are where bugs love to hide. Clean, scoped, and well-structured effects make your code:

  • Easier to debug
  • Easier to test
  • More predictable

And in the React world, predictability is power.

Heroku

Deploy with ease. Manage efficiently. Scale faster.

Leave the infrastructure headaches to us, while you focus on pushing boundaries, realizing your vision, and making a lasting impression on your users.

Get Started

Top comments (0)

ACI image

ACI.dev: The Only MCP Server Your AI Agents Need

ACI.dev’s open-source tool-use platform and Unified MCP Server turns 600+ functions into two simple MCP tools on one server—search and execute. Comes with multi-tenant auth and natural-language permission scopes. 100% open-source under Apache 2.0.

Star our GitHub!