Using the useEffect cleanup function in React

Sometimes you have an useEffect that needs to do some cleanup work and that sounds very abstract so let me give you a more concrete example. Let's say that we execute a function in a useEffect hook that essentially is executed on every keystroke, like in the following example:

const [enteredPassword, setEnteredPassword] = useState(''); const [enteredEmail, setEnteredEmail] = useState(''); useEffect(() => { console.log('Checking form validity!'); setFormIsValid( enteredEmail.includes('@') && enteredPassword.trim().length > 6 ); }, [enteredEmail, enteredPassword])

Now imagine you would do something more complex, like, for example, send an HTTP request to some backend where you check if a username is already in use. You don't wanna do that with every keystroke. Because if you do, that means you're going to be sending a lot of requests. And I don't know about you, but that might be a lot of unnecessary network traffic. So trat's something you wanna avoid. And as I said, even this here, in the previous example, this state updating is something you might not wanna do on every keystroke. Instead, something you might want to do is that you collect a certain amount of keystrokes, or you simply wait for a pause of a certain time duration after a keystroke. And only if the pause is long enough you go ahead and do your thing. For example we send the request to the backend just when we have a pause of 500 miliseconds. Now that's a technique which is called debouncing. We wanna debounce the user input, we wanna make sure we're not doing something on every key stroke but once the user made a pause during typing and with useEffect it's actually easy to implement.

We can use setTimeout, which is a function built into the browser, to wait for example 500 miliseconds until we execute some function. Now in this function we might want to check our form validity or to update our form validity after every 500 miliseconds:

useEffect(() => { setTimeout(() => { console.log('Checking form validity'); setFormIsValid( enteredEmail.includes('@') && enteredPassword.trim().length > 6 ); }, 500); }, [enteredEmail, enteredPassword])

In the useEffect function, so in the function you pass as a first argument, you can do something we haven't done before: you can return something. Now the something you return here has to be one specific thing: it needs to be a function itself. So here you can also return a function, for example, an anonymous arrow function, but it could also be a named function just as for all the places where I'm using anonymous functions. This is a so called cleanup function.

This cleanup function runs whenever this useEffect function runs. Before it runs, except for the very first time when it runs, this function cleanup function will run. So the cleanup function runs before every new side effect function execution and before the component is removed and it does not run before the first side effect function execution

useEffect cleanup function example:

useEffect(() => { const identifier = setTimeout(() => { console.log('Checking form validity!'); setFormIsValid( enteredEmail.includes('@') && enteredPassword.trim().length > 6 ); }, 500); return () => { console.log('CLEANUP'); clearTimeout(identifier); } }, [enteredEmail, enteredPassword])