How the React useEffect Hook Works?

March 8th, 2023
Share:
How the React useEffect Hook Works?

What is the React useEffect hook?

useEffect is one of the basic hooks in React. It is used in functional components to perform component side effects such as:

  • Fetching data from the API
  • Work with browser API (local storage, session storage, etc.)
  • Work with setInterval and setTimeout
  • Updating DOM
  • Measuring the position of the DOM elements
  • Subscription/unsubscription to an event (window.addEventListener/window.removeEventListener)

useEffect includes several component life cycles:

  • Mounting (componentDidMount in the class components)
  • Updating (componentDidUpdate)
  • Unmounting (componentWillUnmount)

How does the React useEffect work?

The useEffect hook takes two parameters - the function initializing effect logic and the dependencies array (optional). Dependencies values are props or state.

  • If the dependencies array is not specified, the function passed to the useEffect will be called after every component re-render.
useEffect(() => {
someFunction();
})
  • If the dependencies array is specified as an empty array, the function passed to the useEffect will be called once after the first render of the component.
useEffect(() => {
someFunction();
}, [])
  • If the dependencies array has values, the function passed to the useEffect will be called after the first render of the component and when any value in the array is updated.
useEffect(() => {
someFunction();
}, [dependency1, dependency2,...])

It is also worth noting that every value referenced inside useEffect should be specified in the dependencies array.

Cleaning up an effect

Sometimes, we need to run a clean-up function before the component is unmounted. For example, when we want to unsubscribe from an event or reset a timer. To do this, we need to return a function from the useEffect where we describe the clean-up effect logic.

const handleScroll = () => {};
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, [])

In the code above, we subscribe to the scroll event after the component render. Then we unsubscribe from the event before removing the component from the page.

Look at another example, timeout, and imagine that the useEffect depends on the value prop.

useEffect(() => {
const timerId = setTimeout(() => {...}, 500);
return () => clearTimeout(timerId);
}, [value])

As in the first example, the timer will be set after the component's render and reset when the component is removed from the page. Additionally, whenever the value prop is updated, the timer will be reset and set again.

How to use useEffect with async-await?

At first glance, you might think that since the first parameter in the useEffect is a function, we can specify the async directly to it.

//Bad case
useEffect(async () => {
const result = await apiCall();
}, [])

But the truth is that the function passed to the useEffect should either not return anything or return a clean-up function. The async function returns Promise, which is against the useEffect rules. We need to create an async function inside the hook to fix that.

const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async() => {
try {
const result = await apiCall();
setData(result)
} catch (error) {
console.log(error);
}
};
fetchData ();
}, [])

We can also move the getData function outside the useEffect to a different method.

const [data, setData] = useState([]);
const fetchData = useCallback(async () => {
try {
const result = await apiCall();
setData(result)
} catch (error) {
console.log(error);
}
}, []);
useEffect(() => {
fetchData();
}, [fetchData])

But in this case, we need to wrap the fetchData into the useCallback hook and specify the function as a dependency. If we don`t wrap it in the useCallback, it will lead to an infinite loop. Why?

The useEffect makes comparisons and checks if the dependencies have been updated. In our example, we call the fetchData function after the first component render that updates the state. If the state is updated, the component renders again. With every render, the reference to the fetchData function is also updated. Therefore, the result of comparing dependencies returns false, and useEffect calls the fetchData again. So, in turn, we get an infinite loop.

useCallback hook helps to keep the same function instance between component renders and, as a result, prevents infinite calls of fetchData.

You may read this post to learn more about the useCllaback hook.

Conclusion

useEffect is a basic and one of the essential hooks in React that allow performing component side-effects.

By default, effects fire after every render. With the empty dependency array, they perform after the first render of the component. With dependencies, effects re-create if one of its dependencies updates. Additionally, since such effects are defined within the component, they can access the component props and state.

There are also some rules which better to use:

  • Use useEffect at the top level of your component. Don`t use it in conditions, loops, or nested functions.

  • Don`t use async directly to the useEffect function

  • Perform only one task using useEffect.

    Suppose you have a lot of component logic. In that case, it is better to split it into separate functions and use several useEffects to prevent unnecessary re-renders and make the code cleaner and readable.

We hope this article was helpful to you! Thanks for reading!

Subscribe icon

Subscribe to our Newsletter

Sign up with your email address to receive latest posts and updates