In this article,  we will learn how to cleanup resources using useEffect hook in functional component. In other words, how to mimic the ComponentWillUnMount behaviour in functional component using useEffect Hook. 

React’s useEffect cleanup function saves applications from unwanted behaviours like memory leaks by cleaning up effects. In doing so, we can optimise our application’s performance.


What is the useEffect cleanup function?

Just like the name implies, the useEffect cleanup is a function in the useEffect Hook that allows us to tidy up our code before our component unmount. When our code runs and reruns for every render, useEffect also cleans up after itself using the cleanup function.

The useEffect Hook is built in a way that we can return a function inside it and this return function is where the cleanup happens. The cleanup function prevents memory leaks and removes some unnecessary and unwanted behaviours.

Note that you don’t update the state inside the return function either:

useEffect(() => {
console.log('useFffect called')
window.addEventListener('mousemove', logMousePosition)

return () => {
console.log('Component unmounting code')
window.removeEventListener('mousemove', logMousePosition)
}
}, [])

Why is the useEffect cleanup function useful?

As stated previously, the useEffect cleanup function helps developers clean effects that prevent unwanted behaviours and optimises application performance.

However, it is pertinent to note that the useEffect cleanup function does not only run when our component wants to unmount, it also runs right before the execution of the next scheduled effect.

In fact, after our effect executes, the next scheduled effect is usually based on the dependency(array):


When should we use the useEffect cleanup?

Let’s say we have a React component that fetches and renders data. If our component unmount before our promise resolves, useEffect will try to update the state (on an unmounted component) and send an error that looks like this:

Warning: Can't perform a React State update on a unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all the subscriptions and asynchronous tasks in a useEffect cleanup function.

To fix this error, we use the cleanup function to resolve it.

According to React’s official documentation, “React performs the cleanup when the component unmount. However… effects run for every render and not just once. This is why React also cleans up effects from the previous render before running the effects next time.”

The cleanup is commonly used to cancel all subscriptions made and cancel fetch requests. Now, let’s write some code and see how we can accomplish these cancellations.


Example Cleanup resources in useEffect Hook


Let's create HookMouse functional component and below code to it.

import React, { useState, useEffect } from 'react'

function HookMouse() {
const [x, setX] = useState(0)
const [y, setY] = useState(0)

const logMousePosition = e => {
console.log('Mouse event')
setX(e.clientX)
setY(e.clientY)
}

useEffect(() => {
console.log('useFffect called')
window.addEventListener('mousemove', logMousePosition)

return () => {
console.log('Component unmounting code')
window.removeEventListener('mousemove', logMousePosition)
}
}, [])
return (
<div>
Hooks - X - {x} Y - {y}
</div>
)
}

export default HookMouse

Create another functional component called MouseContainer and button in JSX and onClick of button toggle the HookMouse component.

import React, { useState } from 'react'
import HookMouse from './HookMouse'

function MouseContainer() {
const [display, setDisplay] = useState(true)
return (
<div>
<button onClick={() => setDisplay(!display)}>Toggle display</button>
{display && <HookMouse />}
</div>
)
}

export default MouseContainer

Import MouseContainer in App.js component

import React, { Component } from 'react'
import './App.css'

import MouseContainer from './components/MouseContainer'


class App extends Component {
render() {
return (
<div className="App">
<MouseContainer />
</div>
)
}
}

export default App


In the MouseContainer functional component I'm going to import useState from react and create a display state variable, the variable name is display the setter is setDisplay and the initial value is true.

Next in the JSX, I am going to add a button to toggle this display variable between true and false button toggle display, on click we call setDisplay passing in not off display so we are toggling the display value.

If this display variable is set to true we render the hook mouse component display and HookMouse component.

Finally, in App.js i will only include the MouseContainer component, if we now save all the files and take a look at the browser, we should have a button and the mouse coordinates. When I move the mouse around you can see the coordinates updating and the log statement is printed every time the mouse moves.

Now I'm going to click on the toggle display button this will effectively unmount the component from the Dom.

Now when I try to move the mouse around we can see a warning in the console below the warning we can see that the statement mouse event is still being logged so even though the component has been removed, the event listener which belongs to that component is still listening and executing and

this is what the warning indicates as well can't perform a react State update on an unmounted component it indicates a memory leak in your application or in simpler words react is telling us hey you have unmounted the component but when you move your mouse around, you're asking me to update the state variables for x and y-coordinates.

The only problem is that component has been unmounted when you unmount a component make sure you cancel all your subscriptions and listeners in other words clean up after your component by the way this Mouse event is being logged from HookMouse from log Mouse position, this log statement right here.

Now how do we handle the cleanup code, if you are familiar with class components it was pretty straightforward. We had the componentWillUnMount lifecycle hook where we could simply add window dot remove event listener mouse move and the handler is log Mouse position.

But how do we mimic this lifecycle functionality in use effect, well it turns out to be pretty simple the function that is passed to useEffect can return a function which will be executed when the componentwillunmount, so whatever you return is basically your cleanup function

so from this arrow function passed to use effect we return another cleanup function return a function let's log a statement component un mounting code and we remove the event listener mouse move and log mouse position.

Let's save the files and test this out I move the mouse around we see the log statements a toggle display and you can see the statement component and mounting code in the console this means our event listener is now removed. If I move the mouse around again you can see that we don't have the warning or the log statements from before.