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.

/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
  • 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} />
      </div>
    );
  }
}
Good
class EngineRow extends React.Component {
  render () {
    return <div className="row">{this.props.running}</div>;
  }
}

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 (
      <ul>
        {this.state.comments.map(({body, author}) => {
          return <li>{body}—{author}</li>;
        })}
      </ul>
    );
  }
}
Good
// CommentList.js

class CommentList extends React.Component {
  render() {
    return (
      <ul>
        {this.props.comments.map(({body, author}) => {
          return <li>{body}—{author}</li>;
        })}
      </ul>
    );
  }
}
// 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.

Be first to comment

Leave a Reply