React is an awesome UI library (not framework) and has gained a lot of love. A colleague/friend, in a dialogue on React said he doesn't find working with React so interesting because there are just so many options to do one thing. After some research and casual interviews, I realized that was one of the reasons developers run into challenges working with this library.
Different ways to create and manage a React component, tons of state management tools emerging, and more are the spotlight of these challenges. What we can do today is bring the most accepted way of doing things in React (based on community choice) to the table and discuss them.
Along the road, we will visit some helpful topics and terms in React which could sometimes feel intimidating to beginners. Such topics include:
- Component Ownership
- Prop Validation
- Component Interaction
- Component Defaults
- ES6+ Components (and difference from normal components)
- Stateless Components
This is article is strictly basic so you might find yourself reading what you already know. If you are looking for more advanced topics, you can go through our numerous React articles.
The Conventional React Component
By conventional, I mean what is common and you might have seen in most code repositories and articles:
var Hello = React.createClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
Keep in mind that conventional does not mean best practice. This was what the React official docs promoted and that is why it is very common.
React.createClass
function must be passed in an argument of Object
type. This object defines a react component. The render
property is required and the most important property. It is responsible for parsing the HTML in JavaScript, JSX.
Web applications are only interesting when they are dynamic. Any UI library will always provide a way to pass data around the system and React's idea is via the props
object. So when you see the following JSX:
<h1>My name is {name}</h1>
We are telling React that when the component is being invoked, name property with a value should be passed along just as we saw in the above example's render:
<Hello name="World" />
The render
method takes a component to output and the DOM to output to. That makes a basic anatomy of a React Component.
States & Props
Dynamic apps must need to pass data around it's system. In React, data movement happens mostly among components and external services that provide the original data (eg HTTP, localStorage).
Props are immutable and dumb which means they can only be passed from parent components down and cannot be changed. This poses a challenge because, modern apps do not rely on having all of it's states ready on page load. Ajax or Events could happen and when data returns, someone needs to be responsible for making updates. This is where React states
comes in.
On initializing React, we define an initial state and keep the state in sync with the props. Once the state is updated, the props can then be easily kept in sync:
var Counter = React.createClass({
getInitialState: function() {
return {
counter: 0
};
},
render: function() {
return <div>
<h2>{this.props.name}</h2>
{this.state.counter}
</div>;
}
});
ReactDOM.render(
<Counter name={'Counter'} />,
document.getElementById('container')
);
The example is still dumb because the responsibility of state in the context can still be achieved with props
. While we go deeper, we will see better reasons for the existence of states.
Component Ownership (a.k.a Parenting)
This is quite straight-forward. If a component renders another component in it's render
method, the renderer is the owner (parent) of the rendered. The renderer owns the rendered component and has control over it. I prefer to refer to this as parenting.
Let's take a look at another example:
var CounterDisplay = React.createClass({
render: function(){
return <div>{this.props.counterProp}</div>
}
})
var Counter = React.createClass({
getInitialState: function() {
return {
counter: 0
};
},
render: function() {
// Child component rendered
// React will throw an error if the the DOM doesn't have a single parent
return <div>
<h2>{this.props.name}</h2>
<CounterDisplay counterProp={this.state.counter}></CounterDisplay>
</div>;
}
});
ReactDOM.render(
<Counter name={'Counter'} />,
document.getElementById('container')
);
Counter
now renders another component, CounterDisplay
. Counter
is responsible for managing and syncing CounterDisplay
's props. You can see how we are passing the state down the child component via props. The props
could as well be named counter
just as the state but this can be very confusing to beginners so I gave it a different name.
Component Interaction
What if we had a button (or more) in a child component that incremented and decremented an integer (state) managed in a parent component. What do we do?
React component interaction comes in two forms: flow of data from parent to child component and flow of data from child to parent. We have already seen how the parent to child flow goes which was achieved using props
.
To achieve child to parent data flow in react, we use handlers passed in to the child component via the parent component as props. The parent knows that such activity could occur in it's child so it sets up a handler for when the activity occurs. More like events:
var CounterDisplay = React.createClass({
render: function(){
// Calls the handler props once events are fired
return <div>
<div>{this.props.counterProp}</div>
<button onClick={this.props.incrementCounter}>+</button>
<button onClick={this.props.decrementCounter}>-</button>
</div>
}
})
var Counter = React.createClass({
getInitialState: function() {
return {
counter: 0
};
},
handleIncrement(){
// Update counter state
this.setState({counter : this.state.counter+1});
},
handleDecrement(){
// Update counter state
this.setState({counter : this.state.counter-1});
},
render: function() {
// Pass down handlers to CounterDisplay component
return <div>
<h2>{this.props.name}</h2>
<CounterDisplay
counterProp={this.state.counter}
incrementCounter={this.handleIncrement}
decrementCounter={this.handleDecrement}></CounterDisplay>
</div>;
}
});
ReactDOM.render(
<Counter name={'Counter'} />,
document.getElementById('container')
);
The CounterDisplay
component has click events. Their handlers are not found anywhere in the component. Rather, the handlers are handled by the parent component, Counter
. The handler in turn updates the state using this.setState()
method and the counter display props gets updated without being explicit about that.
Component Defaults
Not only states have the ability to enjoy initial values with the getInitialState
method. If need be, you can also setup default values for props
which will be used on the component load. To achieve this, you can make use of getDefaultProps
method:
getDefaultProps: function() {
return {
name: 'Counter'
};
},
This is very useful for setting up default values in an app.
Prop Validation
One nice thing about React components that I have seen developers enjoy and emphasize on is it's usability. You can throw any component around in your app to do what it was intended to do provided you abide by it's rule. When making my own re-usable components, how do I make my own rules? Prop validation is the answer to your question.
Validation as the name goes helps you feel assured that the data flowing into your component is structured the way you expect it to be structured. You can't afford to treat a custom component data as string
and the user treats the data like an array
.
var CounterDisplay = React.createClass({
render: function(){
// Calls the handler props once events are fired
return <div>
<div>{this.props.counterProp}</div>
<br />
<button onClick={this.props.incrementCounter}>+</button>
<button onClick={this.props.decrementCounter}>-</button>
</div>
},
// Setup validation for each props
propTypes: {
// Must be a number
counterProp: React.PropTypes.number.isRequired,
// Must be functions
incrementCounter: React.PropTypes.func.isRequired,
decrementCounter: React.PropTypes.func.isRequired
}
})
If all you need is to validate the type and not whether it exists or not, you can omit the isRequired
:
propTypes: {
// Should be a number
counterProp: React.PropTypes.number,
// Should be functions
incrementCounter: React.PropTypes.func,
decrementCounter: React.PropTypes.func
}
Class Component (ES6)
React.createClass
is not the only possible way to create valid React component. With ES6 (which is really cool), we can use classes to create React components. This means that instead of object as argument with properties, we use class members to define behavior:
// Extends React.Compoent
class Comment extends React.Component {
// Render method now a class member rather than
// object property
render(){
return <h1>{this.props.name}</h1>;
}
}
React.render(<Comment name={'Comment'}/>, document.getElementById('container'));
The name of the component is the class name and the class extends React.Component
to inherit its functionalities.
Setting States with Classes
If you go the class route, things will change. There are minor changes to the way we have always done things including setting initial states:
class Comment extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0
};
}
render(){
return <h1>{this.props.name}</h1>;
}
}
React.render(<Comment name={'Comment'}/>, document.getElementById('container'));
The initial state is now set in the class constructor rather than using getInitialState
. It is important to always pass the props back the parent class using super(props)
.
Default Props and Validation with Class
Just as setting initial state is different, default properties and property validation is different. We treat the methods responsible for performing this actions like static members of the Component class which they are:
// Validation
Comment.propTypes = {
counterProp: React.PropTypes.number.isRequired,
incrementCounter: React.PropTypes.func.isRequired,
decrementCounter: React.PropTypes.func.isRequired
};
// Defaults
Comment.defaultProps = {
name: 'Counter'
};
There are few more minor difference but the above are what you should look out for. You can read Todd's article on the differences.
Stateless Components
When a component has no reason to deal with states, it is irrelevant to use a class to define the component. You can make use of just functions if all is required in a component is props:
function CommentDisplay(props) {
return <div>
<div>{this.props.counterProp}</div>
<br />
<button onClick={this.props.incrementCounter}>+</button>
<button onClick={this.props.decrementCounter}>-</button>
</div>
}
This is much simpler!!!
Wrap Up
The topics we have covered today are what will guide you to real app development in React.
Scotch covered a series of articles which presents how you can use React in real world problems by abstracting presentation components and container components.
Managing state in React could be tricky and that's why tools like Redux exist. With time, we will get out tutorials on how to manage your React application state with Redux.