//

REACT BEST PRACTICES & PATTERNS

REACT BEST PRACTICES & PATTERNS

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.js
2/components/common/Link.js
3/components/common/...
4/components/forms/TextBox.js
5/components/forms/...
6/components/layout/App.js
7/components/layout/Navigation.js
8/components/layout/...
9/components/pages/Home.js
10/components/pages/Account/index.js
11/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
  • defaultProps
  • propTypes

Let's look at an example:

1class Car extends React.Component {
2 constructor (props) {
3 super(props);
4
5 this.state = { running: false };
6
7 this.handleClick = () => {
8 this.setState({running: !this.state.running});
9 };
10 }
11
12 componentWillMount () {
13 // add event listeners (Flux Store, WebSocket, document, etc.)
14 },
15
16 componentDidMount () {
17 // React.getDOMNode()
18 },
19
20 componentWillUnmount () {
21 // remove event listeners (Flux Store, WebSocket, document, etc.)
22 },
23
24 get engineStatus () {
25 return (this.state.running) ? "is running" : "is off";
26 }
27
28 render () {
29 return (
30 <div onClick={this.handleClick}>
31 {this.props.make} {this.engineStatus}
32 </div>
33 );
34 },
35}
36
37Car.defaultProps = {
38 make: 'Toyota'
39};
40
41Car.propTypes = {
42 make: React.PropTypes.string
43};

Class Names

Use classNames to manage conditional classes.

bad

1get classes () {
2 let classes = ['my-component'];
3
4 if (this.state.active) {
5 classes.push('my-omponent-active');
6 }
7
8 return classes.join(' ');
9}
10
11render () {
12 return <div className={this.classes} />;
13}

Good

1render () {
2 let classes = {
3 'MyComponent': true,
4 'MyComponent--active': this.state.active
5 };
6
7 return <div className={classnames(classes)} />;
8}

Formatting Props

Wrap props on newlines for exactly 2 or more.

Bad

1<Car
2 make="Oldsmobile" />

Good

1<Car make="Oldsmobile" />

Bad

1<Car make="Oldsmobile" year="1950" name="88"/>

Good

1<Car
2 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},
4
5render () {
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 : null
8 }
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}
6
7class 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.js
2
3class CommentList extends React.Component {
4 getInitialState () {
5 return { comments: [] };
6 }
7
8 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 }
17
18 render () {
19 return (
20
21 {this.state.comments.map(({body, author}) => {
22 return * {body}{author};
23 })}
24
25 );
26 }
27}

Good

1// CommentList.js
2
3class CommentList extends React.Component {
4 render() {
5 return (
6
7 {this.props.comments.map(({body, author}) => {
8 return * {body}{author};
9 })}
10
11 );
12 }
13}
14// CommentListContainer.js
15
16class CommentListContainer extends React.Component {
17 getInitialState () {
18 return { comments: [] }
19 }
20
21 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 }
30
31 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}`;
3
4 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}
4
5render () {
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},
4
5render() {
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}
6
7MyComponent.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.items
4 };
5}

Good

1getInitialState () {
2 return {
3 items: this.props.initialItems
4 };
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 () { /*...*/ },
2
3render () {
4 return <div onClick={this.click} />;
5}

Good

1handleClick () { /*...*/ },
2
3render () {
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 event
4 }
5
6 render () {
7 return <Ownee onDelete={this.handleUpdate} />;
8 }
9}
10
11class Ownee extends React.Component {
12 render () {
13 return <div onChange={this.props.onUpdate} />;
14 }
15}
16
17Ownee.propTypes = {
18 onUpdate: React.PropTypes.func.isRequired
19};

Using PropTypes

Use PropTypes to communicate expectations and log meaningful warnings.

1MyValidatedComponent.propTypes = {
2 name: React.PropTypes.string
3};

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;
2
3if (this.props.condition) {
4 optionalElement = (<div></div>);
5}
6
7return (
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<Component
2 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.