The preceding example showed you how to initialize the state of a container component by making an API call in the componentDidMount() lifecycle method. However, the only populated part of the component state is the users collection. You might want to populate other pieces of state that don't come from API endpoints.
For example, the error and loading state messages have default values set when the state is initialized. This is great, but what if the code that is rendering UserListContainer wants to use a different loading message? You can achieve this by allowing properties to override the default state. Let's build on the UserListContainer component:
import React, { Component } from 'react';
import { fromJS } from 'immutable';
import { users } from './api';
import UserList from './UserList';
class UserListContainer extends Component {
state = {
data: fromJS({
error: null,
users: []
})
};
// Getter for "Immutable.js" state data...
get data() {
return this.state.data;
}
// Setter for "Immutable.js" state data...
set data(data) {
this.setState({ data });
}
// When component has been rendered, "componentDidMount()"
// is called. This is where we should perform asynchronous
// behavior that will change the state of the component.
// In this case, we're fetching a list of users from
// the mock API.
componentDidMount() {
users().then(
result => {
// Populate the "users" state, but also
// make sure the "error" and "loading"
// states are cleared.
this.data = this.data
.set('error', null)
.set('users', fromJS(result.users));
},
error => {
// When an error occurs, we want to clear
// the "loading" state and set the "error"
// state.
this.data = this.data
.set('loading', null)
.set('error', error);
}
);
}
render() {
return <UserList {...this.data.toJS()} />;
}
// Called right before render, you can use this method
// to update the state of the component based on prop
// values.
static getDerivedStateFromProps(props, state) {
return {
...state,
data: state.data.set(
'loading',
state.data.get('users').size === 0 ? props.loading : null
)
};
}
}
UserListContainer.defaultProps = {
loading: 'loading...'
};
export default UserListContainer;
The loading property no longer has a default string value. Instead, defaultProps provides default values for properties. The new lifecycle method is getDerivedStateFromProps(). It uses the loading property to set the loading state the state. Since the loading property has a default value, it's safe to just change the state. The method is called before the component mounts and on subsequent re-renders of the component.
The challenge with this new React 16 method is that it's called on initial render and on subsequent re-renders. Prior to React 16, you could use the componentWillMount() method for code that you only want to run prior to the initial render. In this example, you have to check whether there are values in the users collection before setting the loading state to null – you don't know if this is the initial render or the 40th render.
Let's see how we can pass state data to UserListContainer now:
import React from 'react';
import { render } from 'react-dom';
import UserListContainer from './UserListContainer';
// Renders the component with a "loading" property.
// This value ultimately ends up in the component state.
render(
<UserListContainer loading="playing the waiting game..." />,
document.getElementById('root')
);
Here's what the initial loading message looks like when UserList is first rendered:

Just because the component has state doesn't mean that you can't allow for customization. Next, you'll learn a variation on this concept—updating component state with properties.