The life cycle comprises three main stages:
-
When the component is being created (known as mounting).
-
When the component is being updated.
-
When the component is being removed from the DOM (known as unmounting).
You can think about this like:
- Mounting – Birth of your component
- Update – Growth of your component
- Unmount – Death of your component
These methods are called at specific points in the rendering process. You can use them to perform actions based on what's happening on the DOM.
componentDidMount()
, for example, is called immediately after a component is rendered to the DOM.componentWillUnmount()
is called immediately before a component is removed from the DOM.
-
Initializing/mounting: For example, what happens when the component is created and inserted into the DOM? Was an initial state set? Methods are:
constructor()
static getDerivedStateFromProps()
render()
componentDidMount()
-
Updating: For example, did an event happen that changed the state? What happens when a component is being re-rendered? Methods are:
static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()
-
Destruction/unmounting: For example, what needs to happen when we're done with the component? Method is:
componentWillUnmount()
Talking Points:
-
Here they are in diagram form.
-
Again, you don't need to write these methods — they happen automatically, just like constructors did before we explicitly wrote one. You only have to worry about these if you want to change something in them. But, if you do, it's important to understand them!
-
Note that
getDerivedStateFromProps()
,shouldComponentUpdate()
, andgetSnapshotBeforeUpdate()
are not included in this diagram because they are less commonly leveraged.
constructor(props) {
super(props);
}
-
This is in the first part of the component life cycle: initializing/mounting. Like any JavaScript class, the
constructor()
method is called when a component is instantiated (i.e., when it's first rendered). We've already used these, but let's take a look in more detail. -
In a class constructor, you must call
super()
before you do anything else. So, a React componentconstructor()
in its most basic form looks as is shown here. -
Even though you may see the constructor syntax used in some online resources, the best practices for how to write the initial state for components have changed in recent years. Instead, we can create a component and directly define the starting state without ever having to use a constructor as of React 16. The following will be a detailed guide of the older syntax as it will be likely that legacy React application might still use this syntax. Being familiar with both syntaxes is important.
constructor(props) {
super(props)
this.state = {
fruits: props.fruits
}
}
-
You don't need to define a constructor if that's all it does, however. This happens automatically when your component is invoked. A common use of the constructor is to initialize state using the props passed to the component, as we've been doing.
-
This constructor sets the initial
fruits
state of the component to thefruits
prop passed to the component.
state = {
fruits: this.props.fruits
}
Talking Points:
-
Calling state this way is best practice for new React project. React will automatically set the state and handle the constructor behind the scene.
-
Note that constructor is no longer used. To get access to props passed down from the parent, you can access it through
this.props
. Another thing React does behind the scene to make cleaner code!
Another common use of the constructor()
method is to bind class methods.
For example, we could set:
this.myFunctionName = this.myFunctionName.bind(this)
In JavaScript classes, methods aren't bound by default. So, if you pass a component's method to a child component without binding it, its context will be bound to the child and not the parent, causing it to behave in a way other than how you intended.
Here's an example:
class FruitTable extends Component {
constructor(props) {
super(props)
this.state = {
fruits: props.fruits
};
this.addFruit = this.addFruit.bind(this);
}
addFruit(fruit) {
this.setState(prevState => ({
fruits: prevState.fruits.concat(fruit)
}))
}
}
Talking Points:
-
Notice that, in the constructor,
this.addFruit
is bound to the class:this.addFruit = this.addFruit.bind(this);
-
Now, if we pass
this.addFruit
to a child component as anonChange
callback, it will be bound toFruitTable
and will update its state when it's invoked. -
You should not call
setState()
in the constructor if you need local state. Instead, assign the initial state tothis.state
directly in the constructor. -
You only need a constructor for two purposes: 1. Initializing local state by assigning an object to
this.state
. 2. Binding event handler methods to an instance.
class FruitTable extends Component {
state = {
fruits: props.fruits
}
}
addFruit = (fruit) => {
this.setState(prevState => ({
fruits: prevState.fruits.concat(fruit)
}))
}
}
Talking Points:
- Using the new arrow function, we can bind the
this
context during the method definition. It will behave the same way as described above with theconstructor()
andbind()
.
getDerivedStateFromProps()
is invoked right before calling the render()
method. It should return an object to update the state or null to update nothing.
getSnapshotBeforeUpdate()
is invoked right before the most recently rendered output is committed (e.g., to the DOM).
Note:: These methods exists for rare use cases in which the state depends on changes in props over time (getDerivedStateFromProps()
) or if you need to handle something such as scroll position before an update (getSnapshotBeforeUpdate()
). It's a best practice to explore other lifecycle methods before using these.
Talking Points:
- These methods are not as commonly used and can make your code overly verbose and your components overly complicated.
- Alternatives: If you want to perform a supplementary effect in response to props, use
componenentDidUpdate()
, which we will discuss later. - Alternatives: Discuss memoization helpers for re-computing data when a prop changes.
- Alternatives: To reset its state on prop change, make the component fully controlled or uncontrolled with a key instead of using this method.
The componentDidMount
method is called once, immediately after your component is rendered to the DOM. If you want to make an AJAX request when your component first renders, do so here (not in the constructor or in componentWillMount()
).
In the following example, we fetch data from the server, then set the state of the component using the response:
componentDidMount() {
fetch(`http://api.com/${this.props.id}`)
.then(response => response.json())
.then(data => this.setState(prevState => ({ data })));
}
class FruitTable extends Component {
componentDidMount() {
document.addEventListener('dragover', this.handleDragStart)
}
componentWillUnmount() {
document.removeEventListener('dragover', this.handleDragStart)
}
handleDragStart = (e) => {
e.preventDefault()
this.setState({ dragging: true});
}
}
Talking Points:
-
Another common use for
componentDidMount()
is to bind event listeners to your component. You can then remove the event listeners incomponentWillUnmount()
, which is the only method in the last part of the component life cycle (when the component is being removed from the DOM). In the example seen on this slide, we bind and unbind an event listener for a drag-drop component. sNote that memory issues will arise when using event listeners without unbinding them after use! -
You may call
setState()
immediately incomponentDidMount()
. It will trigger an extra rendering but will happen before the browser updates the screen.
This is the one method that every React class component must have. It is called in two parts of the component life cycle: At the beginning, when the component is being initiated/mounted, and when the component is being updated.
- In
render()
, you return JSX usingthis.props
andthis.state
.
The following component renders a single prop
and a single state
key: A car model and a speed. Once this component is mounted, its speed
state will increase by 1
every second. You can try it out yourself in this CodePen.
class Car extends Component {
constructor(props) {
super(props)
// Sets initial state to either the speed prop, or 0 if the speed prop
// is not passed to the component.
this.state = {
speed: this.props.speed || 0
}
// Binds this.incrementSpeed to class.
// This way, when it's used as a callback for setTimeout,
// `this` refers to the Car class.
this.incrementSpeed = this.incrementSpeed.bind(this);
}
componentDidMount() {
// Calls this.incrementSpeed after one second.
window.setTimeout(this.incrementSpeed, 1000);
}
incrementSpeed() {
// Sets the speed state to one unit higher than it was previously.
this.setState(prevState => ({ speed: prevState.speed + 1 }));
// Recursive method.
// Increases speed by 1 again after one second.
window.setTimeout(this.incrementSpeed, 1000);
}
render() {
return (
<div>
This {this.props.model} is going {this.state.speed} miles per hour!
</div>
)
}
}
constructor()
The initial state is set to either the speed
prop, or 0
if the speed
prop is not passed to the component.
We bind this.incrementSpeed
to the class, so that when it's used as a callback for setTimeout
, this
refers to the Car
class.
componentDidMount()
Use window.setTimeout
to call this.incrementSpeed
after one second (1,000 ms).
incrementSpeed()
this.setState(prevState => ({ speed: prevState.speed + 1 }))
: The speed
state is set to one higher than it was previously — we add 1
.
window.setTimeout(this.incrementSpeed, 1000)
: The incrementSpeed
method is recursive — it invokes itself as the timeout callback. After one second, window.setTimeout
will call this.incrementSpeed
again. The speed
will increase by 1
, and a new timer will be set to do it again.
class Car extends Component {
// Sets initial state to either the speed prop, or 0 if the speed prop
// is not passed to the component.
state = {
speed: props.speed || 0
}
componentDidMount() {
// Calls this.incrementSpeed after one second.
window.setTimeout(this.incrementSpeed, 1000);
}
// Binds this.incrementSpeed to class using the arrow syntax.
// This way, when it's used as a callback for setTimeout,
// `this` refers to the Car class.
incrementSpeed = () => {
// Sets the speed state to one unit higher than it was previously.
this.setState(prevState => ({ speed: prevState.speed + 1 }));
// Recursive method.
// Increases speed by 1 again after one second.
window.setTimeout(this.incrementSpeed, 1000);
}
render() {
return (
<div>
This {this.props.model} is going {this.state.speed} miles per hour!
</div>
)
}
}
- You should never set
state
inrender()
—render()
should only react to changes in state or props, not create those changes. render()
should be a "pure" function. It does not modify component state, it returns the same result every time it’s invoked, and it does not directly interact with the browser.
shouldComponentUpdate()
lets React know if a component’s output is not affected by the current change in state or props. The default returns true
and re-renders on every state change.
If shouldComponentUpdate()
returns false
, then render()
and componentDidUpdate()
will not be invoked.
shouldComponentUpdate(nextProps, nextState)
Talking Points:
- In the vast majority of cases, you should rely on the default behavior to re-render on every state change and will not need to invoke
shouldComponentUpdate()
. shouldComponentUpdate()
only exists as a performance optimization, and you shouldn't rely on it to "prevent" a re-rendering.
componentDidUpdate(prevProps, prevState, snapshot)
// Example expanding our previous render() example:
componentDidUpdate(prevProps) {
// Don't forget to compare props!
if (this.props.model !== prevProps.model) {
this.fetchData(this.props.model);
}
}
Talking Points:
componentDidUpdate()
is invoked immediately after updating occurs and is a good place to operate on the DOM when the component has been updated.- This is also a good place to do network requests as long as you compare the current props to previous props.
- This method is not called for the initial render.
- You may call
setState()
immediately incomponentDidUpdate()
, but it must be wrapped in a condition, or you’ll cause an infinite loop and a re-rendering. - The re-rendering may not be visible to the user, but it can kill your performance!
- As mentioned before,
componentDidUpdate()
will not be invoked ifshouldComponentUpdate()
returnsfalse
.
React class components have life-cycle methods that are invoked at certain stages of their "lives" on the DOM. Some of the life-cycle methods you'll use frequently include:
constructor()
: Initializes state, binds methods.componentDidMount()
: Makes AJAX requests, gets DOM refs, binds event listeners, setsstate
if necessary.componentWillUnmount()
: Unbinds event listeners, performs other clean-up.componentDidUpdate()
: Updatesstate
based on changes in components.render()
: Returns markup/UI.