In React applications development, there are times when we may want some piece of code to be executed after a component has been rendered. For example, we may want some configuration settings loaded after the application starts execution. In another example, we may want to run some piece of code in response to a change in a specific state variable.
To execute some code logic outside React’s virtual DOM, we need to wait until React has rendered the component, and then invoke useEffect
hook to run a function that executes the side effect code.
In this post, we will examine side effects, and the need to invoke useEffect hook to asynchronously run a function to execute a side effect code. We will also consider how we can control the execution of side effect code with dependencies.
Table of Contents
- What are Side Effects?
- useEffect Runs Side Effect Function
- Parts of Side Effect Function
- When Does useEffect Execute?
- Summary
What are Side Effects?
React, as you may know, is a javascript library for building user interfaces. It is used for building visual elements in web and mobile applications. However, many useful applications are not complete with only UI elements. They may interact with resources that are outside of UI presentation.
For example, applications may need to interact with resources such as files, browsers DOM, localStorage, fetch data from databases and external API, etc. Such interactions with resources outside React are referred to as side effects. Such application codes outside React’s UI presentation are executed with useEffect
hook.
useEffect
Runs Side Effect Function
As ealier indicated, a React application may need to interact with resources outside of the UI code. We have referred to such external interactions as side effects. For any React component, if there is the need to execute side effects, we need to inform React about the function that needs to be called to interact with external resources.
React useEffect
hook is used to call a function that executes outside of React’s virtual DOM. Thus, useEffect
hook is used to call a function that runs side effect code. It accepts a mandatory function as the first argument and an optional dependency array as second argument. The code inside the function passed to the useEffect
hook is the side effect code we intend to run. It has the following signature:
// useEffect function signature
useEffect(side_effect_function, [optional_dependency]);
The function which is called to execute side effect code can be a named function or an anonymous function. Executing side effect code with anonymous function is the most common practice. For example, the following code passes an anonymous function to useEffect
:
// execute side effect code with anonymous function
useEffect(() => {
document.title = 'Understanding useEffect Hook';
});
The alternative call, which is less common, is to pass the name of the function that executes side effect code. An example is shown below:
// execute side effect code with named function
useEffect(setTitle);
// side effect function
function setTitle() {
document.title = 'Understanding useEffect Hook';
}
From the above code, it can be observed that setTitle()
interacts with the browser’s document
object. This document
object is outside of React’s UI presentation. Since this is a side effect, we execute setTitle()
with a useEffect
call on line 2.
Parts of Side Effect Function
A side effect function, which is run by useEffect
hook, has two parts. These are the side effect code and the cleanup code. The parts of the side effect function are explained below:
- Side Effect Code: This is the actual side effect code that is run by the function. It may include tasks such as fetching data from an API, connecting to a database, manipulating the browser’s DOM, interacting with the browsers localStorage, etc. In sum, this is the main task that the side effect function performs.
- Cleanup Code (Optional): This is the code to be executed when the component is about to be unmounted from the DOM. For example, if the side effect code performs tasks which involve allocating memory, registering events, or other resource usages, then this is the part where cleanup must be done. This part is where any allocated memory must be freed, registered events must be unregistered, etc.
Within the side effect function, the side effect code is mainly the code in the body of the side effect function excluding a return statement.
useEffect(() => {
/*
* the side effect code for the function somewhere here.
* This is the part of the code with return statement excluded
*/
document.title = 'Hello React';
});
From listing 4, the side effect code is the code in the body of the anonymous function passed to the useEffect
hook. This is the actual task that we want the function to perform. In this example, the function sets the title of the HTML document. The side effect code does not include a return statement.
If there is the need to perform any cleanup, it must be done by another function. For React to know that a cleanup will need to be performed before the component unmounts, we will need to return the function that performs the cleanup from the side effect function passed to useEffect
.
From the code in listing 5 below, the function called to perform side effect returns a function that will need to be called to perform cleanup:
useEffect(() => {
// the side effect code for the function somewhere here
/* when the component is about to be unmounted, the
* return function below will be called to perform
* any cleanup before component unmount is done
*/
return () => {
// perform cleanup, such as unregistering events, release
// of resources created and used in side effect, etc.
};
});
We can observe that there is a return
statement on line 8. As can be seen, line 8 returns a function. This is a cleanup function. When present, React will remember this function but will not call it immediately. This cleanup function will be called to perform any cleanup of resources allocated when React is about to unmount the component within which the side effect was performed.
Just as a return
statement is optional in any javascript function, the cleanup function is also optional. This is because not all side effect codes use memory allocating resources or subscribe to some events. Such side effect codes do not require cleanup. In such cases, there is no need to return a cleanup function..
Since a cleanup function is optional, it implies that we can write a side effect code without returning a cleanup function. For example:
useEffect(() => {
// interacting directly with the browser document is side effect
document.title = 'React Applications Development';
});
Considering the above code, no cleanup is required to be done after the execution of the side effect code. Due to this, we do not return any cleanup function.
When Does useEffect Execute?
A useEffect
hook is always executed when the component first mounts (first render), and optionally on component updates (re-render). This implies that a useEffect
hook will be invoked at least once.
The first execution occurs after the component first renders. Whether it will execute again or not depends on some specified props and or state variables defined as the second argument, a dependency array.
To better understand when a useEffect
hook usage, we will look at when side effect code executes and different ways in which we can control its subsequent execution.
1. Component Mount and All Updates
By default, a useEffect
will always run when the component is rendered for the first time, and subsequently execute whenever the component updates. In other words, it will execute on all component renders, whether on first render (mount) or re-renders (updates). This is the default uncontrolled behaviour.
To execute a useEffect
hook anytime on component mount and component updates, we will need to pass only the first argument, which is the side effect function, to useEffect
hook. In this case, the second argument, which is the dependency array, should not be passed.
useEffect(side_effect_function);
As can be seen, we are only passing the side effect function as the first argument without passing the second argument to useEffect
. An example is shown below:
useEffect(() => {
// side effect code here
});
The following code is a working example of useEffect
hook that runs a side effect function when the component is first mounted and when the component is updated.
// perform necessary imports
import React, {useState, useEffect} from "react";
const App = () => {
// state variable for heading text
const [heading, setHeading] = useState('Sample Text');
// call the argument function to set the document title
// when this App component first mounts in the DOM and
// anytime the App component is updated
useEffect(() => {
// set the title of the HTML document
document.title = heading;
});
return (
<div style={{textAlign: 'center'}}>
<h2>{heading}</h2>
<hr />
<div style={{marginTop: '20px'}}>
<label>Enter Text: </label>
<input type='text' onChange={(e) => setHeading(e.target.value)} />
</div>
</div>
)
}
export default App;
We have said that a useEffect
hook will always run at least once: when the component in which it is contained is mounted for the first time.
Since a useEffect
hook is executed after component mount, the HTML document title is set to the heading
state variable, which by then has the value 'Sample Text'
. Hence the title of the HTML document will be set to Sample Text
when the document load is completed.
We have also indicated that subsequent execution of side effect code depends on the second argument passed to useEffect
. Since we did not pass the second argument to the useEffect
call, the side effect code will always be executed when the component updates.
On line 23, in listing 9, an onChange
event handler, setHeading
, updates state variable heading
as we enter text in the input
control. The heading
state variable is also set to the <h2></h2>
tag on line 18
, causing the App
component to update.
An update to the App
component triggers the useEffect
call to set the title of the document to the state variable heading
. The result is that whenever we type in the input
control, the entered text is updated in the h2
heading text, and is subsequently set as the HTML document title.
As we type in the input
control, the App
component is re-rendered to update the heading text. However, because we did not pass any dependency value to useEffect
, the side effect code is executed whenever the App
component updates. Thus, the change in the heading text is also reflected in the change in the title of the HTML document.
2. Component Mount and Conditional State / Prop Update
Recall that the second argument passed to useEffect
is a dependency information. It tells that, after the first execution on mount, any further execution of the side effect code depends on some condition or state of some specified variables. React allows us to specify multiple states or props which, when they change, trigger further execution of the side effect code.
We can specify the dependent state or prop variables as an array and pass this dependency array as the second argument to useEffect
hook call:
useEffect(side_effect_function, [dependency_state_variables]);
In the above code, we have specified the second argument to useEffect
as an array. The dependency array contains the state or prop variables that the side effect code will depend on for further execution. For example:
useEffect(() => {
// side effect code here
}, [var1]);
After the first execution of the side effect code on component mount, further execution will only occur when var1
changes.
If there are multiple state or prop variables that the side effect code will depend on for further execution, they will need to be separated by comma in the array delimiter:
useEffect(() => {
// side effect code here
}, [var1, var1, var3]);
From the above code listing, we are actually saying that, after the first execution on component mount, the side effect code should be executed again when a change occurs in either var1
, var2
, or var3
. Thus, further execution of the side effect code depends on changes in the state or prop variables specified in the dependency array.
To demonstrate with an example, we will consider one similar to the previous demonstration. Rather than update the title of the HTML document as we type in the input
control, we will rather update only the content heading. The HTML document title will be updated to the entered heading text after a button is clicked.
// perform necessary imports
import React, {useState, useEffect} from "react";
const App = () => {
// state variable for heading text
const [heading, setHeading] = useState('Sample Text');
const [canSetTitle, setCanSetTitle] = useState(false);
// call the argument function to set the document title
// when this App component first mounts in the DOM and
// anytime the App component is updated
useEffect(() => {
// set the title of the HTML document
document.title = canSetTitle ? heading : document.title;
}, [canSetTitle]);
const onInputChange = (e) => {
setHeading(e.target.value);
setCanSetTitle(false);
}
return (
<div style={{textAlign: 'center'}}>
<h2>{heading}</h2>
<hr />
<div style={{marginTop: '20px'}}>
<label>Enter Text: </label>
<input type='text' onChange={onInputChange} />
<button onClick={() => { setCanSetTitle(true) }}>Set Title</button>
</div>
</div>
)
}
export default App;
On line 7
, we declare boolean
state variable canSetTitle
, which we will use to determine whether we can set the title of the HTML document or not. Since this is a side effect, we execute with a useEffect
call on line 12
.
The side effect code will be executed when the App
component first mounts. However its subsequent execution will be done when canSetTitle
state variable changes. This is because we have passed canSetTitle
state variable as a dependency in the array passed as second argument to the the useEffect
call.
As we type in the input
control, the onInputChange
event handler is called to set the content heading. This time, the document title is not set automatically. The onInputChange
event handler rather sets canSetTitle
state variable to false
.
A click on the button updates canSetTitle
to true
. This update in the state variable, set as a dependency on line 15
, causes the side effect code to execute. If canSetTitle
is true
, then the document title is set to the heading text. However, if canSetTitle
is false
, the title is maintained.
As we type in the input
control, the content heading updates. However, the title of the HTML document does not update until the button
is clicked. This is because the side effect code which updates the HTML document title depends on changes in the state variable canSetTitle
.
3. Component Mount Only
Sometimes, we may want to execute side effect code only once when a component is mounted (first render). In this case, we wouldn’t want the side effect code to be executed any further. For example, after an App
component is mounted, we may want to load some configuration settings from an external server. Once the configuration settings are loaded, we may not want to load the same configuration settings again just because there is a UI update.
To execute a side effect code only once, we will need to pass an empty array as the second argument to the useEffect
hook call:
useEffect(() => {
// side effect code here
}, []);
The side effect code will be executed when the component is first mounted. However, because the dependency array is empty, there wouldn’t be any prop or state variable to trigger further execution. The result is that the side effect code will never be executed again.
The following code listing is a slight update to listing 13 seen earlier. For brevity, let’s see line 12 to line 15 in listing 13, shown below:
useEffect(() => {
// set the title of the HTML document
document.title = canSetTitle ? heading : document.title;
}, [canSetTitle]);
The only update we will do is to clear canSetTitle
from the dependency array. This will mean that we will have an empty array as the second argument to the useEffect
hook call.
useEffect(() => {
// set the title of the HTML document
document.title = canSetTitle ? heading : document.title;
}, []);
Every other part of listing 13 must be left intact. The complete code after the performing the update is shown below:
// perform necessary imports
import React, {useState, useEffect} from "react";
const App = () => {
// state variable for heading text
const [heading, setHeading] = useState('Sample Text');
const [canSetTitle, setCanSetTitle] = useState(false);
// call the argument function to set the document title
// when this App component first mounts in the DOM and
// anytime the App component is updated
useEffect(() => {
// set the title of the HTML document
document.title = canSetTitle ? heading : document.title;
}, []);
const onInputChange = (e) => {
setHeading(e.target.value);
setCanSetTitle(false);
}
return (
<div style={{textAlign: 'center'}}>
<h2>{heading}</h2>
<hr />
<div style={{marginTop: '20px'}}>
<label>Enter Text: </label>
<input type='text' onChange={onInputChange} />
<button onClick={() => { setCanSetTitle(true) }}>Set Title</button>
</div>
</div>
)
}
export default App;
Compared to listing 13, the code listing above has an empty array passed to the useEffect
hook call on line 15. With empty array dependency, the side effect code will not execute again after its first execution on component mount.
As we type in the input
control, the content heading updates. However, the document title does not change after clicking on the button
. This is because the useEffect
hook call has an empty dependency array. Therefore, the side effect code does not execute when there is component update (re-render).
Summary
A React application may interact with code or resources that are outside React’s UI presentation. Such interactions are known as side effects. They include interactions such as connection with external API, browser DOM, localStorage, etc.
To execute a side effect code, the React useEffect
hook is invoked to run the function that executes side effect code. The side effect function may optionally return another function that will be called to perform side effect cleanups when the component is about to be unmounted from the DOM.
React useEffect
will always run a side effect function when the component is first mounted in the DOM. Its subsequent execution depends on a second argument passed to useEffect
. This second argument is specified as an array of state or prop variables whose updates trigger the further execution of the side effect code.
If an empty array is passed as dependency, then the useEffect
hook call will never be run again. If state or prop variables are specified, then the side effect code will execute when any of the specified state or prop variables are updated.