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.
1/actions/ActionTypes.js2/components/common/Link.js3/components/common/...4/components/forms/TextBox.js5/components/forms/...6/components/layout/App.js7/components/layout/Navigation.js8/components/layout/...9/components/pages/Home.js10/components/pages/Account/index.js11/components/pages/...12/core/...13/constants/...14/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:
1class Car extends React.Component {2 constructor (props) {3 super(props);45 this.state = { running: false };67 this.handleClick = () => {8 this.setState({running: !this.state.running});9 };10 }1112 componentWillMount () {13 // add event listeners (Flux Store, WebSocket, document, etc.)14 },1516 componentDidMount () {17 // React.getDOMNode()18 },1920 componentWillUnmount () {21 // remove event listeners (Flux Store, WebSocket, document, etc.)22 },2324 get engineStatus () {25 return (this.state.running) ? "is running" : "is off";26 }2728 render () {29 return (30 <div onClick={this.handleClick}>31 {this.props.make} {this.engineStatus}32 </div>33 );34 },35}3637Car.defaultProps = {38 make: 'Toyota'39};4041Car.propTypes = {42 make: React.PropTypes.string43};
Class Names
Use classNames to manage conditional classes.
bad
1get classes () {2 let classes = ['my-component'];34 if (this.state.active) {5 classes.push('my-omponent-active');6 }78 return classes.join(' ');9}1011render () {12 return <div className={this.classes} />;13}
Good
1render () {2 let classes = {3 'MyComponent': true,4 'MyComponent--active': this.state.active5 };67 return <div className={classnames(classes)} />;8}
Formatting Props
Wrap props on newlines for exactly 2 or more.
Bad
1<Car2 make="Oldsmobile" />
Good
1<Car make="Oldsmobile" />
Bad
1<Car make="Oldsmobile" year="1950" name="88"/>
Good
1<Car2 make="Oldsmobile"3 year="1950"4 name="88"/>
Computed Props
Use getters to name computed properties.
Bad
1makeAndName () {2 return `${this.props.make} ${this.props.name}`;3}
Good
1get brand () {2 return `${this.props.make} ${this.props.name}`;3}
Computed State
Prefix compound state getters
with a verb for readability.
Bad
1isAccelerating () {2 return this.state.isRunning && this.state.pushingPedal;3}
Good
1get isAccelerating () {2 return this.state.isRunning && this.state.pushingPedal;3}
These methods MUST return a boolean value.
Ternary render statements
Instead of sub-render statements, use ternary.
Bad
1renderRunning () {2 return <strong>{(this.state.isRunning) ? ' is running.' : ''}</strong>;3},45render () {6 return <div>{this.props.name}{this.renderRunning()}</div>;7}
Good
1render () {2 return (3 <div>4 {this.props.name}5 {(this.state.running)6 ? <span>is running</span>7 : null8 }9 </div>10 );11}
View Components
Compose components into views. Don't create one-off components that merge layout
and domain components
.
Bad
1class EngineRow extends React.Component {2 render () {3 return (4 <div className="row">5 <Engine engine={this.state.running} />6 ```7 );8 }9}
Good
1class EngineRow extends React.Component {2 render () {3 return <div className="row">{this.props.running} ```;4 }5}67class EngineStateView extends React.createClass {8 render () {9 return (10 <EngineRow>11 <Engine engine={this.state.running} />12 </EngineRow>13 );14 }15}
Container Components
A container does data fetching and then renders its corresponding sub-component.
Bad
1// CommentList.js23class CommentList extends React.Component {4 getInitialState () {5 return { comments: [] };6 }78 componentDidMount () {9 $.ajax({10 url: "/my-comments.json",11 dataType: 'json',12 success: function(comments) {13 this.setState({comments: comments});14 }.bind(this)15 });16 }1718 render () {19 return (2021 {this.state.comments.map(({body, author}) => {22 return * {body}—{author};23 })}2425 );26 }27}
Good
1// CommentList.js23class CommentList extends React.Component {4 render() {5 return (67 {this.props.comments.map(({body, author}) => {8 return * {body}—{author};9 })}1011 );12 }13}14// CommentListContainer.js1516class CommentListContainer extends React.Component {17 getInitialState () {18 return { comments: [] }19 }2021 componentDidMount () {22 $.ajax({23 url: "/my-comments.json",24 dataType: 'json',25 success: function(comments) {26 this.setState({comments: comments});27 }.bind(this)28 });29 }3031 render () {32 return <CommentList comments={this.state.comments} />;33 }34}
Cached State in render###
Do not keep state in render
Bad
1render () {2 let make = `Make: ${this.props.make}`;34 return <div>{name}</div>;5}
Good
1render () {2 return <div>{`Make: ${this.props.make}`}</div>;3}
Good
1get make () {2 return `Make: ${this.props.make}`;3}45render () {6 return <div>{this.make}</div>;7}
Compound Conditions
Don't put compound conditions in render
Bad
1render () {2 return <div>{if (this.state.started && this.state.running) { return 'Running..' }</div>;3}
Good
1get isEngineOn() {2 return this.state.started && this.state.running;3},45render() {6 return <div>{this.isEngineOn && 'Running..'}</div>;7}
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
1render () {2 if (this.props.person) {3 return <div>{this.props.car.make}</div>;4 } else {5 return null;6 }7}
Good
1class MyComponent extends React.Component {2 render() {3 return <div>{this.props.car.make}</div>;4 }5}67MyComponent.defaultProps = {8 car: {9 make: 'Toyota'10 }11};
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
1getInitialState () {2 return {3 items: this.props.items4 };5}
Good
1getInitialState () {2 return {3 items: this.props.initialItems4 };5}
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
1click () { /*...*/ },23render () {4 return <div onClick={this.click} />;5}
Good
1handleClick () { /*...*/ },23render () {4 return <div onClick={this.handleClick} />;5}
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.
1class Owner extends React.Component {2 handleUpdate () {3 // handle Ownee's onUpdate event4 }56 render () {7 return <Ownee onDelete={this.handleUpdate} />;8 }9}1011class Ownee extends React.Component {12 render () {13 return <div onChange={this.props.onUpdate} />;14 }15}1617Ownee.propTypes = {18 onUpdate: React.PropTypes.func.isRequired19};
Using PropTypes
Use PropTypes
to communicate expectations and log meaningful warnings.
1MyValidatedComponent.propTypes = {2 name: React.PropTypes.string3};
MyValidatedComponent will log a warning if it receives name of a type other than string.
1<Car brand=536 />2// Warning: Invalid prop `brand` of type `number` supplied to `MyValidatedComponent`, expected `string`.
This component will now validate the presence of name.
1<Car />2// Warning: Required prop `brand` was not specified in `Car`
Using Entities
Use React's String.fromCharCode() for special characters.
Bad
1<div>PiCO · Mascot</div>
Good
1<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
1render () {2 return (3 <table>4 <tr>...</tr>5 </table>6 );7}
Good
1render () {2 return (3 <table>4 <tbody>5 <tr>...</tr>6 </tbody>7 </table>8 );9}
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
1return (<div><ComponentOne /><ComponentTwo /></div>);
Good
1return (2 <div>3 <ComponentOne />4 <ComponentTwo />5 </div>6 );
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.
1var optionalElement;23if (this.props.condition) {4 optionalElement = (<div> … </div>);5}67return (8 <div>9 …10 {optionalElement}11 …12 </div>13);
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.
1return (2 <div>3 {this.props.list.map(function(data, i) {4 return (<Component data={data} key={i} />)5 })}6 </div>7);
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
1<Component attribute={...} anotherAttribute={...} attributeThree={...} />
Good
1<Component2 attribute={...}3 anotherAttribute={...}4 attributeThree={...}5 …6 />
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.