Introduction
This note is a set of examples on the React life-cycle methods and miscellaneous topics on the JSX and the browser memory collection in relation with React. React supports nested components, but this note is limited on non-nested components for simplicity. This note only works on the browser side of the story on React.
Background
React has a list of event style methods. To use React with confidence, particularly if we want to use React with other Javascript libraries, a clear understanding on these life-cycle methods is important. For a simple non-nested React component, the list of these methods is the following:
- getInitialState() - It is invoked once before the component is mounted. The return value will be used as the initial state of the component;
- componentWillMount() - It is invoked once, immediately before the initial rendering;
- componentDidMount() - It is invoked once, immediately after the initial rendering occurs. If you want to integrate with other JavaScript frameworks, perform those operations in this method;
- shouldComponentUpdate(nextProp, nextState) - It is invoked before rendering when new props or state are being received. This method is not called for the initial rendering;
- componentWillUpdate() - It is invoked immediately before rendering when new props or state are being received. This method is not called for the initial rendering;
- componentDidUpdate() - It is invoked immediately after the component's updates are flushed to the DOM. This method is not called for the initial render. You can use this method as an opportunity to operate on the DOM when the component has been updated;
- componentWillUnmount() - It is invoked immediately before a component is un-mounted from the DOM. You can perform any necessary cleanup work in this method;
- render() - This method is to return the HTML content of the component.
This note is to give examples to demonstrate when these event style methods fire and in which order they fire. In the official React document, the "getInitialState()" and "render()" methods are not listed as life-cycle methods, but I feel that they play important roles in a React component's life-cycle.
The attached is a Java Maven project. The "index.html" file contains the hyper-links to the other example "html" files.
If you are interested in exploring the Maven project, you can take a look at this link and this link. If you do not use Java, it is OK because the attached are static "html" files that you can run on any web servers. The React related Javascript libraries are linked from the CDN "https://cdnjs.cloudflare.com/", so you will need the internet access to run the examples.
To Babel or not to Babel? This is the Question
Before the examples of the life-cycle methods, I want to talk about the "JSX" first. The "render()" method is the method for React to return the HTML content of the component. We can use two types of syntax in the "render()" method:
- We can use the Javascript function "React.createElement()";
- We can also use the "JSX" syntax that allows us to type in the HTML content similar to the HTML syntax.
The "0.JSX-or-Not-babel.html" used "JSX" syntax and the "0.JSX-or-Not-javascripts.html" used "React.createElement()" function:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>0.JSX-or-Not-babel.html</title>
<link rel="stylesheet" type="text/css" href="styles/app.css">
</head>
<body>
<div id="content"></div>
<div>
</div>
<div><a href="index.html">Go back</a></div>
</body>
<script type="text/javascript">
console.time('execution');
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.0/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
<script type="text/babel">
var myclass = React.createClass({
componentDidMount: function() {
console.timeEnd('execution');
},
render: function() {
return <div></div>;
}
});
ReactDOM.render(
React.createElement(myclass, null),
document.getElementById('content')
);
</script>
</html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>0.JSX-or-Not-javascripts.html</title>
<link rel="stylesheet" type="text/css" href="styles/app.css">
</head>
<body>
<div id="content"></div>
<div>
</div>
<div><a href="index.html">Go back</a></div>
</body>
<script type="text/javascript">
console.time('execution');
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.0/react-dom.js"></script>
<script type="text/javascript">
var myclass = React.createClass({
componentDidMount: function() {
console.timeEnd('execution');
},
render: function() {
return React.createElement('div', null);
}
});
ReactDOM.render(
React.createElement(myclass, null),
document.getElementById('content')
);
</script>
</html>
- To use React, we need to link both the "react.js" and the "react-dom.js" files in the HTML files;
- If we want to use "JSX" syntax, we need to link the "browser.min.js" file and make the Javascript type = "text/babel";
- The two examples each renders an empty "div" on the browser;
- The "console.time('execution')" and "console.timeEnd('execution')" functions measure the time between the execution of the first Javascript code and the empty "div" is rendered.
If we go to each web page at least once to make sure all the Javascript files are cached and open the Chrome developer tool in the console tab and re-load each page, we can make a comparison on the time spent for the empty "div" to render.
- It is important to note that the times are measured after all the Javascript files are cached, so there is no further caching to improve the performance;
- It is important to note that we only rendered an empty "div" through React. This indicates that if we use React with JSX, we will need to tolerate the 0.5 second startup overhead regardless what we want the web browser to render;
- After some try & errors, I noticed that majority of the 0.5 second is spent by the browser to evaluate/run the code in the "browser.min.js" file.
To babel or not to babel? This is the question
JSX/babel syntax does provide a lot of convenience due to the similarity to the HTML syntax. For some truly single page applications, the 0.5 second overhead may be tolerable because the "browser.min.js" file needs to be evaluated only once for a user session. But if the web page needs to reload, each load will cost the user additional 0.5 second. Regardless what your decision is, it is nice to be informed about the 0.5 second overhead. Of course, I am hoping that the 0.5 second overhead can be removed in the future React releases.
Life-cycle Methods - Mount Process
The "1.life-cycle-mount.html" is used to experiment on the life-cycle methods related to the mounting process and the order in which they are fired.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>1.life-cycle-mount.html</title>
<link rel="stylesheet" type="text/css" href="styles/app.css">
</head>
<body>
<div id="content"></div>
<div>
</div>
<div><a href="index.html">Go back</a></div>
</body>
<script type="text/javascript">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.0/react-dom.js"></script>
<script type="text/javascript">
var myclass = React.createClass({
getInitialState: function() {
console.log('getInitialState called');
return {};
},
componentWillMount: function() {
console.log('componentWillMount called');
},
componentDidMount: function() {
console.log('componentDidMount called');
},
render: function() {
console.log('render called');
return React.createElement('div', null, "Mount methods example");
}
});
window.onload = function() {
var element = React.createElement(myclass, null);
ReactDOM.render(element, document.getElementById('content'));
};
</script>
</html>
- The "1.life-cycle-mount.html" renders a simple "div" on the browser;
- In each of the method, the "console.log()" function is called, so we can check if the method is called and the order in which they are called from the console tab in the developer tool.
Conclusion
- A React component can be mounted to an HTML element by the "ReactDOM.render()" method;
- The life-cycle methods are fired in the following sequence:
- getInitialState
- componentWillMount
- render
- componentDidMount
- Once the mounting process starts, there is no way to stop these methods from firing;
- When the "componentDidMount" method fires, the HTML content is fully rendered in the DOM by React.
Life-cycle Methods - Update Process
The "2.life-cycle-update.html" is used to experiment on the life-cycle methods related to the update process and the order in which they are fired.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>2.life-cycle-update.html</title>
<link rel="stylesheet" type="text/css" href="styles/app.css">
</head>
<body>
<div id="content"></div>
<div>
</div>
<div><a href="index.html">Go back</a></div>
</body>
<script type="text/javascript">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.0/react-dom.js"></script>
<script type="text/javascript">
var myclass = React.createClass({
getInitialState: function() {
return {
title: 'Update methods example',
updateid: 0
};
},
shouldComponentUpdate: function(nextProp, nextState) {
if (! nextState.updateui) {
console.log('shouldComponentUpdate called - return false');
return false;
}
console.log('shouldComponentUpdate called - return true');
return true;
},
componentWillUpdate: function() {
console.log('componentWillUpdate called');
},
componentDidUpdate: function() {
console.log('componentDidUpdate called');
},
updateButtonClick: function() {
this.setState({updateid: this.state.updateid + 1, updateui: true});
},
noupdateButtonClick: function() {
this.setState({updateid: this.state.updateid + 1, updateui: false});
},
render: function() {
console.log('render called');
var s = this.state;
var label = s.title + ' - ' + s.updateid;
var title = React.createElement('div', null, label);
var noupdateButton = React.createElement('button',
{onClick: this.noupdateButtonClick}, 'No-update button');
var updateButton = React.createElement('button',
{onClick: this.updateButtonClick}, 'Update button');
var buttons = React.createElement('div', null, noupdateButton, updateButton);
return React.createElement('div', null, title, buttons);
}
});
window.onload = function() {
var element = React.createElement(myclass, null);
ReactDOM.render(element, document.getElementById('content'));
};
</script>
</html>
- In this example, two buttons will be rendered to the browser. Each button triggers an update by the "setState()" method;
- The "No-update button" sets "{updateui: false}" and the "Update button" sets "{updateui: false}";
- The "shouldComponentUpdate()" method checks the "updateui" property in the "nextState" and returns true or false accordingly.
Load the page into the browser, we can see the two buttons and we can see the "updateid" is rendered as 0 that matches the initial state. Let's open the console tab in the developer tool and click the "No-update button" a couple of times.
In my experiment, I clicked the "No-update button" 15 times, the "shouldComponentUpdate()" method is called 15 times. But the "shouldComponentUpdate()" method returned false, no other life-cycle methods are fired. The "updateid" remains 0 on the browser.
If we now click the "Update button", we can see that the "updateid" is updated to 16 on the browser. In the console, we can see that all the update related life-cycle methods are fired because "shouldComponentUpdate()" returned true.
Conclusion
- An update to the UI/DOM can be triggered by the "setState()" method;
- Upon a "setState()" call, the "shouldComponentUpdate()" is called;
- If "shouldComponentUpdate()" returns false, only the data state is updated, no UI/DOM is updated;
- If "shouldComponentUpdate()" returns true, both the data state and the UI/DOM is updated.
- Upon the "shouldComponentUpdate()" returns true, the other life-cycle methods are fired in the following sequence:
- componentWillUpdate
- render
- componentDidUpdate
- The "componentWillUpdate()" method gives us the chance to do the necessary clean-up work before React starts to update the DOM;
- When the "componentDidUpdate()" method fires, the changes to the DOM are fully ready for other Javascript code to work on.
Life-cycle Methods - Unmount Process
The "3.life-cycle-unmount.html" is used to experiment on the life-cycle method "componentWillUnmount()" in the un-mount process.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>3.life-cycle-unmount.html</title>
<link rel="stylesheet" type="text/css" href="styles/app.css">
</head>
<body>
<div id="content"></div>
<div>
</div>
<div><a href="index.html">Go back</a></div>
</body>
<script type="text/javascript">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.0/react-dom.js"></script>
<script type="text/javascript">
var myclass = React.createClass({
getInitialState: function() {
var size = 20 * 1024 * 1024;
var a = new Array(size);
for (var i = 0; i < size; i++) {
a[i] = i;
}
return {a: a};
},
unmountButtonClick: function() {
var node = ReactDOM.findDOMNode(this);
ReactDOM.unmountComponentAtNode(node.parentNode);
},
componentWillUnmount: function() {
console.log('componentWillUnmount called');
},
render: function() {
var title = React.createElement('div', null, 'Unmount method example');
var unmountButton = React.createElement('button',
{onClick: this.unmountButtonClick}, 'Unmount from DOM button');
var buttons = React.createElement('div', null, unmountButton);
return React.createElement('div', null, title, buttons);
}
});
window.onload = function() {
var element = React.createElement(myclass, null);
ReactDOM.render(element, document.getElementById('content'));
};
</script>
</html>
- In this example, a button is added to the DOM. If we click on the button the component will be un-mounted from the DOM;
- In the "getInitialState()" method, I artificially added a 20M array to the initial state. I will use this large chunk of memory to demonstrate if the garbage collection works on the browser if we un-mount a React component from the DOM in the "Browser Memory Clean-up with React" section.
If we click on the "Unmount from DOM button", the component will be removed from the DOM. We can see that the "componentWillUnmount()" method is called before the component is cleared from the DOM in the console.
Conclusion
- A mounted React component can be un-mounted from the DOM by the "ReactDOM.unmountComponentAtNode()" method;
- Once the un-mount process starts, it cannot be stopped;
- Before the component is removed from the DOM, the "componentWillUnmount()" fires to give us the chance to do some clean-up work.
Browser Memory Clean-up with React
While I was studying an Angular memory leak problem, I learned the technique to check the browser memory through this link. In the Chrome browser, we can use "Shift + ESC" to launch the Chrome task manager.
With the "3.life-cycle-unmount.html" paged loaded, we can see the memory used by the process serving this page. If we click the "Unmount from DOM button" button to un-mount the React component, we should expect the garbage collection to take place to collect the un-used memory.
The garbage collection may not happen immediately after the component is un-mounted. It may take a while. If it does not happen, we can go to the "Timeline" tab in the development tool to trigger the garbage collection by clicking on the "trash" icon.
With this small test, we should have the peace of mind that React should be free from memory leak problems, if we write our code consciously without leaving dead references to the components and their states. At least it is true for the version of React used in this note.
Points of Interest
- This note is a set of examples on the React life-cycle methods and miscellaneous topics on the JSX and the browser memory collection in relation with React;
- I hope you like my postings and I hope this note can help you one way or the other.
History
Revision - 5/12/2016