DEV Community

Srijan Karki
Srijan Karki

Posted on

The Proper Use of useEffect in React: A Comprehensive Guide

In the realm of React development, the useEffect hook is a powerful tool introduced in React 16.8. It allows functional components to perform side effects, providing a way to synchronize a component with an external system. However, due to the initial hype and the lack of comprehensive documentation when hooks were first released, many developers have misused useEffect, leading to suboptimal and sometimes problematic code. This article aims to clarify when and how to use useEffect effectively, with practical examples to aid understanding.

Understanding useEffect

useEffect react hook

The official React documentation defines useEffect as a hook that lets you "synchronize a component with an external system." This means it is designed to manage side effects such as fetching data, setting up subscriptions, and manually changing the DOM in React components.

However, in practice, developers often overuse useEffect, applying it in scenarios where it is not needed. This can result in unnecessarily complex code and potential performance issues.

When Not to Use useEffect

Before diving into examples, let's outline some common scenarios where useEffect is frequently misused:

  1. Handling User Events (Clicks, Input Changes, etc.):
    • Incorrect Approach: Using useEffect to handle user interactions.
    • Correct Approach: Directly use event handlers.
   // Incorrect
   useEffect(() => {
     const handleClick = () => {
       // logic
     };
     document.addEventListener('click', handleClick);
     return () => {
       document.removeEventListener('click', handleClick);
     };
   }, []);

   // Correct
   const handleClick = () => {
     // logic
   };
   <button onClick={handleClick}>Click Me</button>
Enter fullscreen mode Exit fullscreen mode
  • In the incorrect approach, we use useEffect to add an event listener for clicks, which is unnecessary. Instead, we can directly attach the event handler to the button.

2.Transforming Data for Rendering:

  • Incorrect Approach: Using useEffect to transform data before rendering.
  • Correct Approach: Perform transformations directly in the render method or return statement.
   // Incorrect
   useEffect(() => {
     const transformedData = data.map(item => item * 2);
     setTransformedData(transformedData);
   }, [data]);

   // Correct
   const transformedData = data.map(item => item * 2);
   return (
     <div>{transformedData}</div>
   );
Enter fullscreen mode Exit fullscreen mode
  • In the incorrect approach, we use useEffect to transform data and store it in state. Instead, we can transform the data directly in the render method, simplifying the code.

Using useEffect in these situations can lead to excessive re-renders and even infinite loops, where the component continuously updates and rerenders without stopping.

When to Use useEffect

useEffect shines in scenarios where side effects need to be managed. Here are the appropriate uses of useEffect:

1.Fetching Data on Component Mount:

   useEffect(() => {
     const fetchData = async () => {
       const result = await fetch('https://api.example.com/data');
       const data = await result.json();
       setData(data);
     };
     fetchData();
   }, []); // Empty dependency array ensures this runs only once on mount
Enter fullscreen mode Exit fullscreen mode
  • This code fetches data from an API when the component mounts and stores it in the component's state. The empty dependency array ([]) ensures the effect runs only once.

2.Setting Up Intervals or Subscriptions:

   useEffect(() => {
     const intervalId = setInterval(() => {
       console.log('Interval running');
     }, 1000);
     return () => clearInterval(intervalId); // Cleanup on unmount
   }, []);
Enter fullscreen mode Exit fullscreen mode
  • This code sets up an interval that logs a message every second. The cleanup function (clearInterval) ensures the interval is cleared when the component unmounts.

3.Synchronizing State with External Systems:

   useEffect(() => {
     externalSystem.sync(state);
   }, [state]); // Sync whenever state changes
Enter fullscreen mode Exit fullscreen mode
  • This code synchronizes the component's state with an external system whenever the state changes. The dependency array ([state]) ensures the effect runs whenever state changes.

Pitfalls and Best Practices

While useEffect is simple to use, it can introduce complexity if not handled properly. Here are some best practices to avoid common pitfalls:

  • Dependency Arrays: Always include dependencies that your effect relies on. Omitting dependencies or incorrectly specifying them can lead to bugs.
  useEffect(() => {
    // effect logic
  }, [dependency1, dependency2]); // Specify all dependencies
Enter fullscreen mode Exit fullscreen mode
  • Ensure that all variables used inside the effect are listed in the dependency array to prevent unexpected behaviors.

    • Cleanup: Always return a cleanup function if your effect involves side effects that need to be cleaned up (e.g., event listeners, intervals).
  useEffect(() => {
    const handleResize = () => {
      // handle resize
    };
    window.addEventListener('resize', handleResize);
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);
Enter fullscreen mode Exit fullscreen mode
  • This code sets up an event listener for window resize events and ensures it is removed when the component unmounts, preventing memory leaks.

    • Avoiding Multiple useEffect Calls: If you find yourself writing multiple useEffect hooks in a single component, consider if they can be combined or if the logic can be moved elsewhere.
  // Potentially combine related logic
  useEffect(() => {
    const fetchData = async () => {
      const result = await fetch('https://api.example.com/data');
      setData(await result.json());
    };
    fetchData();

    const handleResize = () => {
      // handle resize
    };
    window.addEventListener('resize', handleResize);

    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);
Enter fullscreen mode Exit fullscreen mode
  • This code combines data fetching and setting up a resize event listener into a single useEffect hook, simplifying the component's structure.

Conclusion

The useEffect hook is a valuable tool in React, but its power comes with the responsibility of knowing when and how to use it properly. By understanding its intended use cases and following best practices, you can write cleaner, more efficient React components. Remember, not every side effect needs useEffect, and overusing it can lead to code that is difficult to maintain and debug. Always evaluate your use cases and choose the right tool for the job.

Top comments (6)

Collapse
 
devgod1994 profile image
Linus

Great, really helpful article

Collapse
 
srijan_karki profile image
Srijan Karki

I am glad that it helped you !

Collapse
 
lirena00 profile image
Saksham Kushwaha

Good article, thabks for writing

Collapse
 
srijan_karki profile image
Srijan Karki

Glad to hear that, keep learning !

Collapse
 
danny69 profile image
Danny

thanks

Collapse
 
srijan_karki profile image
Srijan Karki

Thank you for taking the time to read this article.