When you define a class component, you must always define a render()
method, as React calls it to find out what to render.
But render()
isn’t the only method that components can define!
When React instantiates a class component, it checks to see which lifecycle methods the class defines. You’ve already seen one of these: the render()
method. But the other lifecycle methods are optional; React only calls them if they’re defined. They include:
componentDidMount()
— is called after the inital content has been rendered to the DOM.componentDidUpdate(prevProps)
— is called after subsequent updates are rendered to the DOM, and is passed the previous value of this.props
.componentWillUnmount()
— is called before the component is removed from the DOM.
Lifecycle methods are useful for interacting with the “outside world” in response to changes in a React component. For example, you could define a componentDidUpdate()
method that changes your page’s background to a random color each time the form updates — no matter where the update comes from!
import React from 'react'
import ReactDOM from 'react-dom'
import './styles.css'
class ContactForm extends React.Component {
constructor() {
super()
this.state = {
name: "",
nameError: null,
email: "",
emailError: null,
}
this.handleChangeName = this.handleChangeName.bind(this)
this.handleChangeEmail = this.handleChangeEmail.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}
componentDidUpdate() {
let r = Math.floor(Math.random()*255)
let g = Math.floor(Math.random()*255)
let b = Math.floor(Math.random()*255)
document.body.style.backgroundColor = `rgb(${r}, ${g}, ${b})`
}
render() {
return (
<form
className='ContactForm'
onSubmit={this.handleSubmit}>
<label>
<span>Name</span>
<input
value={this.state.name}
onChange={this.handleChangeName}
/>
{this.state.nameError && (
<div className='ContactForm-error'>
{this.state.nameError}
</div>
)}
</label>
<label>
<span>E-mail</span>
<input
value={this.state.email}
onChange={this.handleChangeEmail}
/>
{this.state.emailError && (
<div className='ContactForm-error'>
{this.state.emailError}
</div>
)}
</label>
<button type="submit">
Add
</button>
</form>
)
}
handleChangeName(event) {
this.setState({
name: event.target.value,
nameError: null,
})
}
handleChangeEmail(event) {
let value = event.target.value
this.setState({
email: value,
emailError: isEmailValid(value) ? null : this.state.emailError,
})
}
handleSubmit(event) {
event.preventDefault()
let errors = {}
if (!this.state.name) {
errors.nameError = "You must enter a name."
}
if (!this.state.email) {
errors.emailError = "You must enter an email."
}
else if (!isEmailValid(this.state.email)) {
errors.emailError = "That doesn't look like a valid e-mail."
}
if (Object.keys(errors).length === 0) {
this.props.onAddContact({
name: this.state.name,
email: this.state.email,
})
}
else {
this.setState(errors)
}
}
}
function isEmailValid(value) {
return value.indexOf('@') !== -1
}
ReactDOM.render(
<ContactForm onAddContact={() => {}} />,
document.getElementById('root')
)
There’s one thing to be careful about when using componentDidUpdate()
. If you call this.setState()
inside it, then you’ll cause another update, and thus an infinite loop! You can use this.setState()
, but you should only do so inside an if
statement that prevents it from being called recursively.
#Fetching data on load
In the real world, lifecycle methods are often used to call networking code as components are mounted, updated, and unmounted. For example, you could use a lifecycle method to automatically fetch data once your contact list app has loaded.
In fact, this sounds like a great exercise!
Your task is to fetch initial data using a lifecycle method.
import React from 'react'
import ReactDOM from 'react-dom'
import { getRecords } from './api'
import { Contact, ContactForm, ContactList } from './Contacts'
class App extends React.Component {
constructor() {
super()
this.state = {
contacts: [],
contactsError: null,
}
this.refresh = this.refresh.bind(this)
}
render() {
let content
if (this.state.contactsError) {
content = (
<p>
{this.state.contactsError}
</p>
)
}
else {
content = this.state.contacts.map((contact, i) =>
React.createElement(Contact, { ...contact, key: i })
)
}
return (
<ContactList onClickRefresh={this.refresh}>
{content}
<ContactForm onAddContact={(contact) => {
this.setState({
contacts: this.state.contacts.concat(contact)
})
}} />
</ContactList>
)
}
refresh() {
getRecords().then(
(response) => {
this.setState({
contacts: response.data,
contactsError: null,
})
},
(error) => {
this.setState({
contactsError: "Your contacts couldn't be loaded :-("
})
}
)
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
Finishing this exercise off should only take a few lines of new code. If you get stuck, use a bit of trial and error before checking the solution — you should be able to make it work.
And once it does work, then you’re pretty much done. Like, actually done. Lifecycle methods are the final React feature we’ll cover in this course! But there are still some important loose ends to tie up, so click on through to the next lesson.