Upgrading to React 0.14 in practice
Not so long ago, React 0.14 was released. You can read the full guide on the facebook’s github, but it’s very long and detailed. If you want TL;DR, in addition with an example, read this article.
I will do Warning Driven Development on this public gist.
Legacy
So this is the code (after a few restrictions of my eslint
):
var React = require("react");
var allItems = [];
allItems.push("Buy ingredients for Crock Pot");
allItems.push("Pick up chair at IKEA");
allItems.push("Go see mom");
class TodoList extends React.Component {
constructor(props){
super(props);
this.addEvent = this.addEvent.bind(this);
}
getInitialState() {
return { allItems };
}
render() {
var items = this.props.items.map((item) => {
return <li><TodoItem item={item} /></li>;
});
return (
<div>
<ul>{items}</ul>
<p><NewTodoItem addEvent={this.addEvent} /></p>
</div>
);
}
addEvent(todoItem){
allItems.push(todoItem.newItem);
this.setState({ allItems });
}
}
class TodoItem extends React.Component {
render(){
return <div>{this.props.item}</div>;
}
}
class NewTodoItem extends React.Component {
constructor(props){
super(props);
this.onSubmit = this.onSubmit.bind(this);
}
componentDidMount(){
React.findDOMNode(this.refs.itemName).focus();
}
render(){
return (<form onSubmit={this.onSubmit}>
<input ref="itemName" type="text" />
</form>);
}
onSubmit(event){
event.preventDefault();
var input = React.findDOMNode(this.refs.itemName);
var newItem = input.value;
this.props.addEvent({ newItem });
input.value = '';
}
}
React.render(<TodoList items={allItems} />, APP_ROOT);
This is the console output with old React 0.13:
Two warnings. And the code seems to work. Cool.
Upgrading React
In order to get the newest React, I had to run npm install --save react react-dom
. And this is where we have to stop, because these are two packages to install. Splitting the react
package into two packages is the first change of 0.14 to meet - read more here.
What does the console tell after the upgrade?
5 warnings, all colored red. Not cool.
In fact, 2nd and 3rd warning are the same as before, only the color changed. The 1st and 5th warnings tell about being deprecated. The 4th warning tells about the composition.
The good news is - the code still works. This is the policy of React - if your code is warningless, you are guaranteed to upgrade with no errors. In our example we had 2 warnings, but the concern must have been not very crucial.
Let’s get rid of the warnings one by one.
Warning: React.render is deprecated
As mentioned before, the DOM-related parts of React now make a separate package react-dom
. We simply require
it…
var ReactDOM = require("react-dom");
…and then call render
from it, rather than from react
:
// React.render(<TodoList items={allItems} />, APP_ROOT);
ReactDOM.render(<TodoList items={allItems} />, APP_ROOT);
VoilĂ !
Warning: React.findDOMNode is deprecated
Because we already imported react-dom
, this one can be fixed right away. We use findDOMNode
in two lines, so let’s change the package also there:
// React.findDOMNode(this.refs.itemName).focus();
ReactDOM.findDOMNode(this.refs.itemName).focus();
// var input = React.findDOMNode(this.refs.itemName);
var input = ReactDOM.findDOMNode(this.refs.itemName);
We got rid of the warning, but interestingly enough - we can remove findDOMNode
completely from the code. This is because React 0.14 changes the interpretation of refs (only for built-in DOM components).
// ReactDOM.findDOMNode(this.refs.itemName).focus();
this.refs.itemName.focus();
// var input = ReactDOM.findDOMNode(this.refs.itemName);
var input = this.refs.itemName;
Warning: getInitialState was defined on TodoList, a plain JavaScript class
You might have already noticed that getInitialState
is a dead code. What’s more, the TodoList
component renders a TodoItem
per item in this.props.items
, which is a property. The TodoList
component doesn’t use its state at all.
This kind of property “imitating” the state is a misusage of React and is considered as an anti-pattern. However, I will leave it now since it’s not the concern of this post.
To make the warning disappear without changing the behavior - simply remove getInitialState
method from TodoList
.
Warning: Each child in an array or iterator should have a unique “key” prop
If you still don’t know why key
prop should be set, see one of our earlier posts.
As the rest of the warning message prompts: Check the render method of TodoList
. We need every <li>
tag to have unique key
property. This is easy to achieve, because we already use map
method. map
‘s callback can take another argument being a unique index of each item. So this is how it looks like after the fix:
// var items = this.props.items.map((item) => {
// return <li><TodoItem item={item} /></li>;
// });
var items = this.props.items.map((item, index) => {
return <li key={index}><TodoItem item={item} /></li>;
});
Warning: validateDOMNesting(…): <form>
cannot appear as a descendant of <p>
This one is interesting. Apparently, React 0.14 introduced some new rules of composing the HTML tags. But I don’t see any sign of it on the facebook’s changelog.
We can simply remove the wrapping <p>
tag around NewTodoItem
component:
// <p><NewTodoItem addEvent={this.addEvent} /></p>
<NewTodoItem addEvent={this.addEvent} />
That’s it. The code works the same, but causes no warnings!
Stateless functional components
We can go a little further and try a new feature of React 0.14 - stateless functional components.
The TodoItem
component is stateless, so let’s rewrite it:
// class TodoItem extends React.Component {
// render(){
// return <div>{this.props.item}</div>;
// }
// }
var TodoItem = (props) => {
return <div>{props.item}</div>;
};
Summary
React seems to care very much about its users when it comes to upgrade. The warning messages are often profound and contain links for more information. Additionally, you are not even forced to upgrade your code immediately. This makes the migrations very easy.