useEffect(): The Most Powerful React Hook

🚀 Dive into ReactJS’s dynamic world with the versatile useEffect hook! 🎣

The old ways of using traditional class components are over; now, useEffect reigns supreme, bringing the robust lifecycle functionalities to functional components in ReactJS!

In React, managing side effects is crucial for building robust and efficient applications. useEffect is a React Hook that allows developers to perform side effects in function components.

Understanding how to use useEffect effectively is essential for writing clean and maintainable code.

The useEffect hook stands out as one of the most extensively utilized features in ReactJS. It empowers developers to mimic the behavior of lifecycle methods found in React Class Components within Functional components.

In this article, we’ll explore the useEffect Hook in-depth, covering its syntax, use cases, common pitfalls, and best practices with illustrative examples and scenarios.

What is useEffect?

useEffect is a built-in React Hook that enables developers to perform side effects in function components. Side effects may include data fetching, subscriptions, or manually changing the DOM in React components.

It serves the same purpose as lifecycle methods (such as componentDidMount, componentDidUpdate, and componentWillUnmount) in class components.

✨ What does useEffect offer?

Seamlessly replicate mounting, updating, and unmounting operations.
Simplify code structure with its two indispensable parameters:

1️⃣ Callback function
2️⃣ Optional dependency array

Let’s explore the four distinct variants of useEffect:

  • Execute Once, Similar to componentDidMount: When an empty dependency array is passed as the second argument, the effect is triggered only once after rendering.
  • Run on Every Render (Comparable to the Render Method):The effect runs every time the component renders.
  • Execute on Every Update(Similar to componentWillUpdate or shouldComponentUpdate): The effect is invoked on every update to the component.
  • Cleanup Operation (Comparable to componentDidUnMount): Use useEffect for cleanup tasks after the component unmounts.

Syntax:

The basic syntax of useEffect is as follows:

JavaScript
import React, { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
    // Side effect code goes here
    return () => {
      // Cleanup code (optional) goes here
    };
  }, [dependencies]);
  
  // Component JSX
}
  • The first argument to useEffect is a function that represents the side effect.
  • The second argument is an optional array of dependencies. If provided, the effect will only re-run if any of the dependencies have changed. If omitted, the effect runs after every render.

The second argument is an optional array of dependencies, so we may leave it black array or we can completely remove it.

Scenarios:

@ No Dependency Array: If we don’t provide the dependency array as second argument, then the effect will run on every render.

JavaScript
import React, { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
     // run on every render.. 
  });  // <-- no dependency array passed.
  
  // Component JSX
}

@ Blank Dependency Array: If we pass an empty dependency array as second argument; then it’ll be called once when the component will be mounted. This will work like class component’s componentDidMount() lifecycle method.

JavaScript
import React, { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
     // Runs only on the first render
  }, []);  // <-- empty dependency array passed.
  
  // Component JSX
}

@ Pass the Dependency: if we provide one or many dependencies in dependency array, then this function will run on every time when any dependency value will change or updated. This will mimic the same functionality of componentDidUpdate or shouldComponentUpdate lifecycle method.

JavaScript
import React, { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
     // Runs on every time when dependency value will update.
  }, [dep1, dep2,...,depN]);  // <-- array of dependencies
  
  // Component JSX
}

@ Cleanup Operation: When we pass the return statement with a function (cleanup function) inside the useEffect; then it will perform cleanup or unmounting. React will call this cleanup function each time before the Effect runs again, and one final time when the component unmount (get removed). This Effect will mimic the componentWillUnmount lifecycle method of class components.

JavaScript
import React, { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
     someEventListner();
     
     reutn() => {
       removeSomeEventListner() {
         // do something for cleanup
       }
     }
  }, []);  // <-- array of dependencies
  
  // Component JSX
}

Examples:

# Example 1: Only run the effect once on render

JavaScript
import { useState, useEffect } from "react";
import ReactDOM from "react-dom/client";

function Timer() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    setTimeout(() => {
      setCount((count) => count + 1);
    }, 1000);
  }, []); // <- empty dependency array...

  return <h1>I've rendered {count} times!</h1>;
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Timer />);

Here we added the empty dependency array [] as second argument in useEffect, this function will run only once when component is mounted or rendered.

# Example 2: Run every time when dependency will update

JavaScript
import { useState, useEffect } from "react";
import ReactDOM from "react-dom/client";

function Counter() {
  const [count, setCount] = useState(0);
  const [calculation, setCalculation] = useState(0);

  useEffect(() => {
    setCalculation(() => count * 2);
  }, [count]); // <- count as dependency

  return (
    <>
      <p>Count: {count}</p>
      <button onClick={() => setCount((c) => c + 1)}>+</button>
      <p>Calculation: {calculation}</p>
    </>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Counter />);

Here we pass the count as dependency. If the count value updates, the effect will run again.

# Example 3: Cleanup

Some time we require the cleanup to reduce memory leaks. Subscriptions, event listeners, timeouts, and other effects that are no longer needed should be removed or disposed.

JavaScript
import React, { useState, useEffect } from 'react';
import io from 'socket.io-client';

function RealtimeComponent() {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    const socket = io('https://realtime.example.com');
    socket.on('message', message => {
      setMessages(prevMessages => [...prevMessages, message]);
    });

    return () => {
      socket.disconnect();
    };
  }, []);

  return (
    <div>
      <ul>
        {messages.map((message, index) => (
          <li key={index}>{message}</li>
        ))}
      </ul>
    </div>
  );
}

Here, we establish a WebSocket connection to receive real-time messages. The effect is run once when the component mounts, and we clean up the socket connection when the component unmounts.

Common Pitfalls and Best Practices:

  1. Forgetting Dependency Array: Always specify dependencies in the dependency array to avoid unintended side effects and infinite loops.
  2. Incorrect Cleanup: Ensure to clean up resources (such as event listeners, subscriptions, or timers) in the cleanup function returned by useEffect.
  3. Conditional Effects: Be cautious when using conditionals inside useEffect, as it can lead to unpredictable behavior. Consider using separate useEffect hooks for different conditions.
  4. Performance Optimization: Use the dependency array to optimize performance by specifying only the necessary dependencies.

Conclusion:

useEffect is a powerful tool in React for managing side effects in function components. By understanding its syntax, use cases, and best practices, developers can write cleaner, more maintainable code. Through examples and scenarios, we’ve explored how to handle subscriptions, and avoid common pitfalls. Mastering useEffect is essential for building high-quality React applications.

Embrace efficiency and flexibility in your ReactJS projects with useEffect! 💡

I’d love to hear your thoughts and insights on the topic. Feel free to leave your comments and share with anyone who might find it helpful. Together, let’s keep learning and growing!

Share