DEV Community

Cover image for React’s `useEffect`: Best Practices, Pitfalls, and Modern JavaScript Insights
Harish Kumar
Harish Kumar

Posted on

React’s `useEffect`: Best Practices, Pitfalls, and Modern JavaScript Insights

React’s useEffect hook is a cornerstone of functional component development, enabling developers to handle side effects like data fetching, DOM manipulation, and subscriptions. While incredibly powerful, useEffect is often misunderstood, leading to performance bottlenecks and tricky bugs. This article’ll uncover the best practices for using useEffect, common pitfalls to avoid, and how modern JavaScript can make your React code even more efficient.

It’s crucial also to stay updated on the core JavaScript language that underpins React. My eBook, "JavaScript: From ES2015 to ES2023", is an excellent resource to deepen your knowledge of modern JavaScript features that are essential for React development.

Let’s get started!



What is useEffect?

useEffect is a hook that lets you perform side effects in function components. It’s React’s replacement for lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount.

Syntax Overview:

useEffect(() => {
  // Effect logic
  return () => {
    // Cleanup logic (optional)
  };
}, [dependencies]);
Enter fullscreen mode Exit fullscreen mode

Best Practices for useEffect

1. Use the Dependency Array Correctly

The dependency array ensures that your effect only runs when specific values change. Always include all variables used inside the effect. Missing dependencies can lead to bugs or stale data.

Example:

useEffect(() => {
  console.log(`Current count is: ${count}`);
}, [count]); // Runs only when `count` changes
Enter fullscreen mode Exit fullscreen mode

Pro Tip: Use a linting tool like eslint-plugin-react-hooks to catch missing dependencies.


2. Separate Concerns with Multiple Effects

Each useEffect should handle a single concern. Combining multiple responsibilities in one effect makes your code harder to debug and maintain.

Example:

useEffect(() => {
  console.log("Component mounted");
}, []);

useEffect(() => {
  document.title = `New count: ${count}`;
}, [count]);
Enter fullscreen mode Exit fullscreen mode

3. Always Clean Up Side Effects

To avoid memory leaks, clean up side effects like subscriptions or timers when the component unmounts or the effect re-runs.

Example:

useEffect(() => {
  const timer = setInterval(() => {
    console.log("Timer running");
  }, 1000);

  return () => clearInterval(timer); // Cleanup
}, []);
Enter fullscreen mode Exit fullscreen mode

4. Avoid Overusing useEffect

Not all logic belongs in useEffect. For example, you don’t need useEffect for derived state or simple computations.

Bad Example:

useEffect(() => {
  setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);
Enter fullscreen mode Exit fullscreen mode

Good Example:

const fullName = `${firstName} ${lastName}`;
Enter fullscreen mode Exit fullscreen mode

5. Use Custom Hooks for Reusable Effects

Extract repeated useEffect logic into custom hooks to simplify your components and promote code reuse.

Example:

const useFetchData = (url) => {
  const [data, setData] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      const response = await fetch(url);
      const result = await response.json();
      setData(result);
    };
    fetchData();
  }, [url]);

  return data;
};
Enter fullscreen mode Exit fullscreen mode

Usage:

const data = useFetchData("/api/data");
Enter fullscreen mode Exit fullscreen mode

Common Pitfalls of useEffect

1. Forgetting Dependencies

Missing dependencies can cause your effect to use outdated values or skip necessary updates. Always list every variable used in the effect.

2. Creating Infinite Loops

Updating state inside an effect without proper conditions can create infinite render loops.

Bad Example:

useEffect(() => {
  setCount(count + 1); // Triggers re-render, causing an infinite loop
}, [count]);
Enter fullscreen mode Exit fullscreen mode

Fix:

useEffect(() => {
  if (count < 10) {
    setCount(count + 1);
  }
}, [count]);
Enter fullscreen mode Exit fullscreen mode

3. Memory Leaks

Forgetting to clean up effects like event listeners or timers can lead to memory leaks, especially in larger applications.

Bad Example:

useEffect(() => {
  window.addEventListener("resize", handleResize);
}, []); // Cleanup missing
Enter fullscreen mode Exit fullscreen mode

Fix:

useEffect(() => {
  window.addEventListener("resize", handleResize);
  return () => window.removeEventListener("resize", handleResize);
}, []);
Enter fullscreen mode Exit fullscreen mode

4. Overcomplicating Effects

Don’t overuse useEffect for tasks that can be handled directly in render or with derived state.


How Modern JavaScript Helps in useEffect

React and modern JavaScript go hand-in-hand. Features like async/await, destructuring, and optional chaining can make your useEffect logic cleaner and more efficient.

Example: Using async/await in Effects

useEffect(() => {
  const fetchData = async () => {
    try {
      const response = await fetch("/api/data");
      const result = await response.json();
      setData(result);
    } catch (error) {
      console.error("Error fetching data:", error);
    }
  };
  fetchData();
}, []);
Enter fullscreen mode Exit fullscreen mode

Conclusion

Mastering useEffect is essential for building robust React applications. By following best practices, avoiding common pitfalls, and leveraging modern JavaScript features, you can write clean, maintainable, and efficient code.

Deepen Your Knowledge: Modern JavaScript is the backbone of React. My eBook, "JavaScript: From ES2015 to ES2023", is a complete guide to mastering essential ES features, from destructuring to the latest advancements in ES2023. This knowledge is key to writing efficient, maintainable React code.

👉 Download eBook - JavaScript: from ES2015 to ES2023

javascript-from-es2015-to-es2023

Top comments (11)

Collapse
 
keyru_nasirusman profile image
keyru Nasir Usman

Isn't it better to use a library like tanstack/react-query rather than plain useEffect to make developer's life easier?

Collapse
 
daviddanielng profile image
David Daniel

More dependence, yay JavaScript.

Collapse
 
elvissautet profile image
Elvis Sautet

interesting, have you tried using Remix.js, will reduce client code by 70%

Collapse
 
erickbgomez profile image
Erick B. Gómez

Very useful tips! I've been struggling this hook in my projects, specially for fetching data or syncing with another component states. Thank you for sharing these tips 🙌

Some comments may only be visible to logged-in visitors. Sign in to view all comments.