Is your React component not rendering?
Quick quiz: When a React component loads data from the server in componentWillMount
like this one below, what will it render?

class Quiz extends Component {
componentWillMount() {
axios.get('/thedata').then(res => {
this.setState({items: res.data});
});
}
render() {
return (
<ul>
{this.state.items.map(item =>
<li key={item.id}>{item.name}</li>
)}
</ul>
);
}
}
If you answered “nothing” or “a console error,” congrats!
If you answered “the data I fetched,” keep reading. ;)
State Starts Off Uninitialized
There are two important things to realize here:
- A component’s state (e.g.
this.state
) begins life as null
. - When you fetch data asynchronously, the component will render at least once before that data is loaded – regardless of whether it’s fetched in the
constructor
, componentWillMount
, or componentDidMount
.
Yes, even though constructor
and componentWillMount
are called before the initial render, asynchronous calls made there will not block the component from rendering. You will still hit this problem.
The Fix(es)
This is easy to fix. The simplest way: initialize state
with reasonable default values in the constructor.
For the component above, it would look like this:
class Quiz extends Component {
constructor(props) {
super(props);
this.state = {
items: []
};
}
componentWillMount() {
axios.get('/thedata').then(res => {
this.setState({items: res.data});
});
}
render() {
return (
<ul>
{this.state.items.map(item =>
<li key={item.id}>{item.name}</li>
)}
</ul>
);
}
}
You could also handle the empty data inside render
, with something like this:
render() {
return (
<ul>
{this.state && this.state.items && this.state.items.map(item =>
<li key={item.id}>{item.name}</li>
)}
</ul>
);
}
This is not the ideal way to handle it though. If you can provide a default value, do so.
Trickle-Down Failures
The lack of default or “empty state” data can bite you another way, too: when undefined state is passed as a prop to a child component.
Expanding on that example above, let’s say we extracted the list into its own component:
class Quiz extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
componentWillMount() {
axios.get('/thedata').then(res => {
this.setState({items: res.data});
});
}
render() {
return (
<ItemList items={this.state.items}/>
);
}
}
function ItemList({ items }) {
return (
<ul>
{items.map(item =>
<li key={item.id}>{item.name}</li>
)}
</ul>
);
}
See the problem? When Quiz
first renders, this.state.items
is undefined. Which, in turn, means ItemList
gets items
as undefined, and you get an error – Uncaught TypeError: Cannot read property 'map' of undefined
in the console.
Debugging this would be easier if ItemList
had propTypes
set up, like this:
function ItemList({ items }) {
return (
);
}
ItemList.propTypes = {
items: React.PropTypes.array.isRequired
};
With this in place, you’ll get this helpful message in the console:
“Warning: Failed prop type: Required prop items
was not specified in ItemList
.”
However, you will still get the error – Uncaught TypeError: Cannot read property 'map' of undefined
. A failed propType
check does not prevent the component from rendering, it only warns.
But at least, this way, it’ll be easier to debug.
Default Props
One more way to fix this: you can provide default values for props.
Default props aren’t always the best answer. Before you set up a default prop, ask yourself if it’s a band-aid fix.
Is the default value there just to prevent transient errors when the data is uninitialized? Better to initialize the data properly.
Is the prop truly optional? Does it make sense to render this component without that prop provided? Then a default makes sense.
Wrap Up
In short:
- Async calls during the component lifecycle means the component will render before that data is loaded, so…
- Initialize
state
in the constructor and/or be sure to handle empty data. - Use
PropTypes
to aid debugging - Use default props when appropriate
Watch Out for Undefined State was originally published by Dave Ceddia at Dave Ceddia on January 16, 2017.
CodeProject