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 caseuseEffect(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 theuseEffect
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
useEffect
s to prevent unnecessary re-renders and make the code cleaner and readable.
We hope this article was helpful to you! Thanks for reading!