DEV Community

loading...
Cover image for React useEffect cleanup: How and when to use it

React useEffect cleanup: How and when to use it

Martín Mato
Martin Mato is a self-taught developer who lives and works in Uruguay. Learning and developing for the last 10 years with #react #react-native #angular #dotnet
・2 min read

SnippetCode

Did you ever got the following error?


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

Enter fullscreen mode Exit fullscreen mode

The message is straightforward. We're trying to change the state of a component, even after it's been unmounted and unavailable.

There are multiple reasons why this can happen but the most common are that we didn’t unsubscribe to a websocket component, or this were dismount before an async operation finished.

How can we fix this?

Cleanup function in the useEffect hook.

The useEffect hook is built in a way that if we return a function within the method, this function will execute when the component gets disassociated. This is very useful because we can use it to remove unnecessary behavior or prevent memory leaking issues.

So, if we want to cleanup a subscription, the code would look like this:

useEffect(() => {
    API.subscribe()
    return function cleanup() {
        API.unsubscribe()
    }
})
Enter fullscreen mode Exit fullscreen mode

Don't update the state on an unmounted component

One common implementation is to update the component state once an async function finishes. But what happen if the component unmounts after finishing? It will try to set the state anyway if we not control that.

In a real scenario, it happened to me on React Native that an user can leave a screen before a process ends.

In the following example, we have an async function that performs some operation and while this is running I want render a “loading” message. Once the function finish I will change the state of “loading” and render another message.

function Example(props) {
    const [loading, setloading] = useState(true)

    useEffect(() => {
        fetchAPI.then(() => {
            setloading(false)
        })
    }, [])

    return <div>{loading ? <p>loading...</p> : <p>Fetched!!</p>}</div>
}
Enter fullscreen mode Exit fullscreen mode

But, if we exit the component and fetchAPI ends and sets the loading state, this will prompt the error mentioned at the beginning. So we need to be sure that the component is still mounted when fetchAPI is finished.

function Example(props) {
    const [loading, setloading] = useState(true)

    useEffect(() => {
        let mounted = true
        fetchAPI.then(() => {
            if (mounted) {
                setloading(false)
            }
        })

        return function cleanup() {
            mounted = false
        }
    }, [])

    return <div>{loading ? <p>loading...</p> : <p>Fetched!!</p>}</div>
}
Enter fullscreen mode Exit fullscreen mode

This way we can ask if the component is still mounted. Just adding a variable that will change to false if we dismount.

Extra: Cancel an Axios request

Axios comes with a cancellation option to finish a request before it ends. This is useful besides the cleanup function to prevent memory leaking.

useEffect(() => {
    const source = axios.CancelToken.source()

    const fetchUsers = async () => {
        try {
            await Axios.get('/users', {
                cancelToken: source.token,
            })
            // ...
        } catch (error) {
            if (Axios.isCancel(error)) {
            } else {
                throw error
            }
        }
    }

    fetchData()

    return () => {
        source.cancel()
    }
}, [])
Enter fullscreen mode Exit fullscreen mode

Conclusion

There's a lot of other usages of the cleanup function on the useEffect hook, but I hope this can give you a better perspective of how and when to use it.
Please add any comment or suggestion, I will appreciate. 

Discussion (25)

Collapse
radoncreep profile image
radoncreep

I am just coming from the react docs trying to figure this out I couldn't get their explanation but this!!! Well explained, came across the same error now i understand why and Ik how to fix it. Thank you!

Collapse
otamnitram profile image
Martín Mato Author

Happy to help!

Collapse
kavehkarami profile image
Kaveh Karami • Edited

Thanks for the solution on Axios request.
I have a question.
I'm using an instance of Axios and in this instance, you cant be able to use instanceAxios.CancelToken.source() so I gotta import Axios too.
is there another way to use CancelToken.source() in the instance of Axios like instanceAxios.CancelToken.source()?

Collapse
otamnitram profile image
Martín Mato Author

As far as I know axios by design, cancelToken won't be part of the instance. You may be able to do something similar to this.

const axios = require('axios');
const source = axios.CancelToken.source();
const instance = axios.create();
instance.get('/foo', { cancelToken: source.token });
Enter fullscreen mode Exit fullscreen mode
Collapse
oscarlofwenhamn profile image
oscar.lofwenhamn

Thanks a bunch, that cleared things up! Time to add some let mounted=... to my effects..

Collapse
maskedman99 profile image
Rohit Prasad

Thanks for the solution on axios request.
I had thought of looking into the warning before, but kept procrastinating it. Now I just need to go and apply the fix :)

Collapse
otamnitram profile image
Martín Mato Author

Glad it was helpful for you

Collapse
jimmybutton profile image
Samuel Liedtke

Many thanks, this was really helpful! I got this error "Warning: An update to SomeComponent inside a test was not wrapped in act(...)." when running my tests. Took me a while to figure out this was coming from a fetch() call in a useEffect() method that was updating some state. Applied the "only update state when mounted" fix you suggested and that solved it.

Collapse
samx23 profile image
Sami Kalammallah

Thank you, it solves my clean up definition issue :)

Collapse
neshatademi profile image
Neshat

Very nice simple and clear explanation. Was trying to figure it out in the react docs but this made my day. ✌️

Collapse
otamnitram profile image
Martín Mato Author

Happy to help!

Collapse
kwadoskii profile image
Austin Ofor

Thanks, this helped me.

Collapse
prakudag profile image
Praveen Danagoudru

let mounted....... is awesome :)

Collapse
kamo profile image
KAIDI

Nice explanation, thnks

Collapse
krankj profile image
Sudarshan K J

Thanks! It is very well written. Even novices like me could understand it fully.

Collapse
obkurucu_ profile image
ozankurucu

Thank you this was unbelievable!

Collapse
mkumar100 profile image
mkumar100

Excellent

Collapse
voralagas profile image
Pravin Poudel

there migh be fetchUsers() instead of fetchData() in axios abort code.

Thank you for the great content !!

Collapse
dadeke profile image
Deividson Damasio

Thank you very much! 😀👍

Collapse
bronxsystem profile image
bronxsystem

the axios thing helped me out a lot thats really cool thank you.

Collapse
paras594 profile image
Paras 🧙‍♂️

awesome !!...very well explained

Collapse
oleksiiivanov profile image
Oleksii Ivanov

Thanks for details.

Collapse
krankj profile image
Sudarshan K J

In the last axios example, shouldn't the method call be fetchUsers() and not fetchData()?

Collapse
radoncreep profile image
radoncreep

yes. it should be fetchUsers()

Collapse
trex777 profile image
Manish Kumar Sahu

Thanks Man! This is quite helpful.