In this article we discuss about React best practices & patterns for writing React components, and the recommendations here aim at coding convensions and patterns.
Naming Conventions
Let's look at the file and folder naming convention for React.js components.
Directory Structure
React component directory structure can be organized as follows.
/actions/ActionTypes.js
/components/common/Link.js
/components/common/...
/components/forms/TextBox.js
/components/forms/...
/components/layout/App.js
/components/layout/Navigation.js
/components/layout/...
/components/pages/Home.js
/components/pages/Account/index.js
/components/pages/...
/core/...
/constants/...
/stores/...
Code Organization
Classes
The class definition for can be organized as follows:
- class definition
- constructor
- event-handlers
- component life-cycle events
- getters
- setters
- constructor
- defaultProps
- propTypes
Let's look at an example:
class Car extends React.Component {
constructor (props) {
super(props);
this.state = { running: false };
this.handleClick = () => {
this.setState({running: !this.state.running});
};
}
componentWillMount () {
// add event listeners (Flux Store, WebSocket, document, etc.)
},
componentDidMount () {
// React.getDOMNode()
},
componentWillUnmount () {
// remove event listeners (Flux Store, WebSocket, document, etc.)
},
get engineStatus () {
return (this.state.running) ? "is running" : "is off";
}
render () {
return (
<div onClick={this.handleClick}>
{this.props.make} {this.engineStatus}
</div>
);
},
}
Car.defaultProps = {
make: 'Toyota'
};
Car.propTypes = {
make: React.PropTypes.string
};
Class Names
Use classNames to manage conditional classes.
bad
get classes () {
let classes = ['my-component'];
if (this.state.active) {
classes.push('my-omponent-active');
}
return classes.join(' ');
}
render () {
return <div className={this.classes} />;
}
Good
render () {
let classes = {
'MyComponent': true,
'MyComponent--active': this.state.active
};
return <div className={classnames(classes)} />;
}
Formatting Props
Wrap props on newlines for exactly 2 or more.
Bad
<Car
make="Oldsmobile" />
Good
<Car make="Oldsmobile" />
Bad
<Car make="Oldsmobile" year="1950" name="88"/>
Good
<Car
make="Oldsmobile"
year="1950"
name="88"/>
Computed Props
Use getters to name computed properties.
Bad
makeAndName () {
return `${this.props.make} ${this.props.name}`;
}
Good
get brand () {
return `${this.props.make} ${this.props.name}`;
}
Computed State
Prefix compound state getters
with a verb for readability.
Bad
isAccelerating () {
return this.state.isRunning && this.state.pushingPedal;
}
Good
get isAccelerating () {
return this.state.isRunning && this.state.pushingPedal;
}
These methods MUST return a boolean value.
Ternary render statements
Instead of sub-render statements, use ternary.
Bad
renderRunning () {
return <strong>{(this.state.isRunning) ? ' is running.' : ''}</strong>;
},
render () {
return <div>{this.props.name}{this.renderRunning()}</div>;
}
Good
render () {
return (
<div>
{this.props.name}
{(this.state.running)
? <span>is running</span>
: null
}
</div>
);
}
View Components
Compose components into views. Don't create one-off components that merge layout
and domain components
.
Bad
class EngineRow extends React.Component {
render () {
return (
<div className="row">
<Engine engine={this.state.running} />
```
);
}
}
Good
class EngineRow extends React.Component {
render () {
return <div className="row">{this.props.running} ```;
}
}
class EngineStateView extends React.createClass {
render () {
return (
<EngineRow>
<Engine engine={this.state.running} />
</EngineRow>
);
}
}
Container Components
A container does data fetching and then renders its corresponding sub-component.
Bad
// CommentList.js
class CommentList extends React.Component {
getInitialState () {
return { comments: [] };
}
componentDidMount () {
$.ajax({
url: "/my-comments.json",
dataType: 'json',
success: function(comments) {
this.setState({comments: comments});
}.bind(this)
});
}
render () {
return (
{this.state.comments.map(({body, author}) => {
return * {body}—{author};
})}
);
}
}
Good
// CommentList.js
class CommentList extends React.Component {
render() {
return (
{this.props.comments.map(({body, author}) => {
return * {body}—{author};
})}
);
}
}
// CommentListContainer.js
class CommentListContainer extends React.Component {
getInitialState () {
return { comments: [] }
}
componentDidMount () {
$.ajax({
url: "/my-comments.json",
dataType: 'json',
success: function(comments) {
this.setState({comments: comments});
}.bind(this)
});
}
render () {
return <CommentList comments={this.state.comments} />;
}
}
Cached State in render###
Do not keep state in render
Bad
render () {
let make = `Make: ${this.props.make}`;
return <div>{name}</div>;
}
Good
render () {
return <div>{`Make: ${this.props.make}`}</div>;
}
Good
get make () {
return `Make: ${this.props.make}`;
}
render () {
return <div>{this.make}</div>;
}
Compound Conditions
Don't put compound conditions in render
Bad
render () {
return <div>{if (this.state.started && this.state.running) { return 'Running..' }</div>;
}
Good
get isEngineOn() {
return this.state.started && this.state.running;
},
render() {
return <div>{this.isEngineOn && 'Running..'}</div>;
}
The best solution for this would use a container component
to manage state and pass new state down as props.
Existence of prop
Do not check existence of prop objects. Use defaultProps.
Bad
render () {
if (this.props.person) {
return <div>{this.props.car.make}</div>;
} else {
return null;
}
}
Good
class MyComponent extends React.Component {
render() {
return <div>{this.props.car.make}</div>;
}
}
MyComponent.defaultProps = {
car: {
make: 'Toyota'
}
};
This is only where objects or arrays are used. Use PropTypes.shape to clarify the types of nested data expected by the component.
Setting State from Props
Do not set state from props without obvious intent.
Bad
getInitialState () {
return {
items: this.props.items
};
}
Good
getInitialState () {
return {
items: this.props.initialItems
};
}
Props in getInitialStet is an Anti-Pattern
According to React documentation, using props, passed down from parent, to generate state in getInitialState often leads to duplication of source of truth, i.e where the real data is.
Naming Handler Methods
Name the handler methods after their triggering event.
Bad
click () { /*...*/ },
render () {
return <div onClick={this.click} />;
}
Good
handleClick () { /*...*/ },
render () {
return <div onClick={this.handleClick} />;
}
Handler names should:
- Be present-tense
- Begin with handle
- Follow with the name of the event. (eg:- handleClick)
Naming Events
Use custom event names for ownee events.
class Owner extends React.Component {
handleUpdate () {
// handle Ownee's onUpdate event
}
render () {
return <Ownee onDelete={this.handleUpdate} />;
}
}
class Ownee extends React.Component {
render () {
return <div onChange={this.props.onUpdate} />;
}
}
Ownee.propTypes = {
onUpdate: React.PropTypes.func.isRequired
};
Using PropTypes
Use PropTypes
to communicate expectations and log meaningful warnings.
MyValidatedComponent.propTypes = {
name: React.PropTypes.string
};
MyValidatedComponent will log a warning if it receives name of a type other than string.
<Car brand=536 />
// Warning: Invalid prop `brand` of type `number` supplied to `MyValidatedComponent`, expected `string`.
This component will now validate the presence of name.
<Car />
// Warning: Required prop `brand` was not specified in `Car`
Using Entities
Use React's String.fromCharCode() for special characters.
Bad
<div>PiCO · Mascot</div>
Good
<div>{`PiCO ${String.fromCharCode(183)} Mascot`}</div>
Tables
The browser thinks you're dumb. But React doesn't. Always use tbody in table components.
Bad
render () {
return (
<table>
<tr>...</tr>
</table>
);
}
Good
render () {
return (
<table>
<tbody>
<tr>...</tr>
</tbody>
</table>
);
}
JSX
By following a few general guidelines for handling JSX in components, it becomes far more readable.
Multi-line JSX
Write any JSX which contains nested elements across multiple lines with indentation to enhance readability.
Bad
return (<div><ComponentOne /><ComponentTwo /></div>);
Good
return (
<div>
<ComponentOne />
<ComponentTwo />
</div>
);
Parenthesis
Parenthesis are not technically required with a single line JSX statement, but use them for the sake of consistency.
Conditional JSX
When you have conditional elements that needs to be returned depending on state, props, or another condition, you can declare an empty variable at the top of the render function and only populate it with JSX if the condition is met. When the variable is returned in the render method return statement, it’ll either render the conditional elements, or nothing at all.
var optionalElement;
if (this.props.condition) {
optionalElement = (<div> … </div>);
}
return (
<div>
…
{optionalElement}
…
</div>
);
In-line list iteration
Where possible, iterate over lists of data in-line in the returned JSX unless its internal logic is sufficiently complex enough to warrant moving outside of the return statement and populating an array for rendering.
return (
<div>
{this.props.list.map(function(data, i) {
return (<Component data={data} key={i} />)
})}
</div>
);
Indentation and new line for component attributes
When there are enough attributes on a component that displaying them inline becomes untidy (usually 3 or more), I always display them on multiple lines and indent each one.
Bad
<Component attribute={...} anotherAttribute={...} attributeThree={...} />
Good
<Component
attribute={...}
anotherAttribute={...}
attributeThree={...}
…
/>
Conclusion
These guidelines are by no means authoritative or exhaustive, but I feel they are a good starting point for organising and standardising React components and some of the more common use cases I encounter.