DEV Community

Cover image for How senior devs use "UseEffect Hook" in large scale applications 🌠
keshav Sandhu
keshav Sandhu

Posted on

How senior devs use "UseEffect Hook" in large scale applications 🌠

The useEffect hook in React is a powerful tool for managing side effects in functional components, such as data fetching, updating the DOM, and setting up subscriptions or timers. While basic usage involves running an effect after rendering, advanced use cases unlock its full potential.

πŸ’‘ Advanced Uses of useEffect Hook:


1. Conditional Effects

You can conditionally run side effects based on dependencies by passing an array as the second argument to useEffect. It only runs when one of the dependencies changes.

Example:

useEffect(() => {
  if (userId) {
    fetchData(userId);
  }
}, [userId]);
Enter fullscreen mode Exit fullscreen mode

In this case, fetchData is only called when userId changes.


2. Effect Cleanup (Unsubscribe or Cleanup Resources)

useEffect allows you to return a cleanup function, which is useful when working with subscriptions, event listeners, or timers. This function is executed when the component unmounts or before the effect runs again (if dependencies change).

Example:

useEffect(() => {
  const subscription = subscribeToData(id);

  // Cleanup function to avoid memory leaks
  return () => {
    subscription.unsubscribe();
  };
}, [id]);
Enter fullscreen mode Exit fullscreen mode

In this example, the unsubscribe method is called when the component unmounts or when id changes, ensuring that no subscriptions are left hanging.


3. Running Effects Only Once (on Mount)

To run an effect only once when the component mounts (similar to componentDidMount in class components), pass an empty dependency array []. This tells React to run the effect only after the initial render.

Example:

useEffect(() => {
  // Fetch data once when the component mounts
  fetchInitialData();

  return () => {
    // Cleanup if needed on unmount
  };
}, []);  // Empty array means effect only runs once on mount
Enter fullscreen mode Exit fullscreen mode

4. Effect Dependencies with Objects or Arrays

When working with objects or arrays, you need to ensure that you’re managing dependency changes carefully. React checks if dependencies have changed by reference, not by value, so you may need to memoize objects/arrays with useMemo or useCallback.

Example:

const user = { id: 1, name: 'John' };

useEffect(() => {
  console.log('User changed!');
}, [user]);  // This will re-run on every render because `user` is a new object every time

// Solution: Memoize the object
const memoizedUser = useMemo(() => ({ id: 1, name: 'John' }), []);
useEffect(() => {
  console.log('Memoized user changed!');
}, [memoizedUser]);  // Effect will only run when the memoized object changes
Enter fullscreen mode Exit fullscreen mode

5. Avoiding Unnecessary Re-renders with useCallback and useMemo

When using functions or complex computations as dependencies in useEffect, ensure they are memoized to avoid unnecessary re-renders.

Example:

const fetchData = useCallback(() => {
  // Expensive or asynchronous function
}, [id]);

useEffect(() => {
  fetchData();
}, [fetchData]);  // `useCallback` ensures fetchData reference stays the same unless `id` changes
Enter fullscreen mode Exit fullscreen mode

6. Handling Async Functions in useEffect

Since useEffect cannot directly handle async functions, you can define an async function inside it to manage async operations like data fetching.

Example:

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

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

Alternatively, you can use an immediately invoked function expression (IIFE) to avoid defining fetchData separately.


7. Optimizing Expensive Effects

Some effects, like complex calculations or heavy DOM manipulations, can be expensive. You can optimize these effects by fine-tuning dependencies or leveraging memoization to run them only when necessary.

Example:

useEffect(() => {
  const result = expensiveCalculation(data);
  setProcessedData(result);
}, [data]);
Enter fullscreen mode Exit fullscreen mode

8. Debouncing with useEffect

You can use useEffect to implement debouncing logic, useful for delaying expensive operations like search or API calls until after a user stops typing.

Example:

useEffect(() => {
  const handler = setTimeout(() => {
    handleSearch(query);
  }, 300);

  return () => {
    clearTimeout(handler);  // Cleanup timeout if user types within delay
  };
}, [query]);
Enter fullscreen mode Exit fullscreen mode

Here, the search query is only processed if the user has stopped typing for 300ms.


9. Synchronizing with External State (Redux/Context)

useEffect is often used to synchronize local component state with external global state (like Redux or Context API).

Example:

const someGlobalState = useSelector(state => state.someValue);

useEffect(() => {
  setLocalState(someGlobalState);
}, [someGlobalState]);
Enter fullscreen mode Exit fullscreen mode

10. Listening to Multiple Dependencies

You can make useEffect listen to multiple dependencies. It re-runs if any of the dependencies change.

Example:

useEffect(() => {
  updateUI(value1, value2);
}, [value1, value2]);  // Effect runs when either `value1` or `value2` changes
Enter fullscreen mode Exit fullscreen mode

⚑ Summary:

  • Run effects based on dependencies for fine-tuned control.
  • Memoize objects, arrays, and functions to avoid unnecessary re-renders.
  • Handle async logic inside useEffect with proper error handling.
  • Use cleanup functions to prevent memory leaks, especially with subscriptions, timers, or external resources.

Mastering useEffect will make your React components more efficient and adaptable!

Top comments (0)