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?
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 asnull
. - 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
, orcomponentDidMount
.
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:
You could also handle the empty data inside render
, with something like this:
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:
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:
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.
This can be done a few ways.
defaultProps property
This method works whether your component is a stateless functional one, or a class that inherits React.Component.
defaultProps static property
This method only works for classes, and only if your compiler is set up to support the static initializer syntax from ES7.
Destructuring in render
A default can be provided using the ES6 destructuring syntax right in the render function.
This line says “extract the items
key from this.props
, and if it’s undefined, set it to an empty array”.
Destructuring in arguments
If your component is of the stateless functional variety, you can destructure right in the arguments:
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
- Destructuring syntax is a clean, easy way to provide defaults
Translations
This article has been translated into Korean here.