Forked from Airbnb's React/JSX Style Guide
HubSpot's version of a mostly reasonable approach to React and JSX
- Basic Rules
- Class vs
React.createClassvs stateless - Naming
- Declaration
- Alignment
- Quotes
- Spacing
- Props
- Parentheses
- Tags
- Methods
- Ordering
isMounted- Set State
- Guidelines
- Only include one React component per file.
- However, multiple Stateless, or Pure, Components are allowed per file. eslint:
react/no-multi-comp.
- However, multiple Stateless, or Pure, Components are allowed per file. eslint:
- Always use JSX syntax.
- Do not use
React.createElementunless you're initializing the app from a file that is not JSX.
If you have internal state and/or refs, prefer
class extends React.ComponentoverReact.createClassunless you have a very good reason to use mixins. eslint:react/prefer-es6-classreact/prefer-stateless-function// badconstListing=React.createClass({// ...render(){return<div>{this.state.hello}</div>;}});// goodclassListingextendsReact.Component{// ...render(){return<div>{this.state.hello}</div>;}}
And if you don't have state or refs, prefer normal functions (not arrow functions) over classes:
// badclassListingextendsReact.Component{render(){return<div>{this.props.hello}</div>;}}// bad (since arrow functions do not have a "name" property)constListing=({ hello })=>(<div>{hello}</div>);// goodfunctionListing({ hello }){return<div>{hello}</div>;}
Extensions: Use
.jsextension for React components.Filename: Use PascalCase for filenames. E.g.,
ReservationCard.js.Reference Naming: Use PascalCase for React components and camelCase for their instances. eslint:
react/jsx-pascal-case// badimportreservationCardfrom'./ReservationCard';// goodimportReservationCardfrom'./ReservationCard';// badconstReservationItem=<ReservationCard/>;// goodconstreservationItem=<ReservationCard/>;
Component Naming: Use the filename as the component name. For example,
ReservationCard.jsshould have a reference name ofReservationCard. However, for root components of a directory, useindex.jsas the filename and use the directory name as the component name:// badimportFooterfrom'./Footer/Footer';// badimportFooterfrom'./Footer/index';// goodimportFooterfrom'./Footer';
Do not use
displayNamefor naming components. Instead, name the component by reference.// badexportdefaultReact.createClass({displayName: 'ReservationCard',// stuff goes here});// goodexportdefaultclassReservationCardextendsReact.Component{}
Follow these alignment styles for JSX syntax. eslint:
react/jsx-closing-bracket-location// bad<FoosuperLongParam="bar"anotherSuperLongParam="baz"/>// good<FoosuperLongParam="bar"anotherSuperLongParam="baz"/>// if props fit in one line then keep it on the same line<Foobar="bar"/>// children get indented normally with closing brackets either `after-props`<FoosuperLongParam="bar"anotherSuperLongParam="baz"><Spazz/></Foo>// or `tag-aligned`<FoosuperLongParam="bar"anotherSuperLongParam="baz"><Quux/></Foo>
- Always use double quotes (
") for JSX attributes, but single quotes for all other JS. eslint:jsx-quotes
Why? JSX attributes can't contain escaped quotes, so double quotes make conjunctions like
"don't"easier to type. Regular HTML attributes also typically use double quotes instead of single, so JSX attributes mirror this convention.
```javascript
// bad
<Foo bar='bar' />
// good
<Foo bar="bar" />
// bad
<Foo style={{ left: "20px" }} />
// good
<Foo style={{ left: '20px' }} />
// good
<Foo bar='"bar"' />
```
Always include a single space in your self-closing tag.
// bad<Foo/>// very bad<Foo/>// bad<Foo/>// good<Foo/>
Always use camelCase for prop names.
// bad<FooUserName="hello"phone_number={12345678}/>// good<FoouserName="hello"phoneNumber={12345678}/>
Use explicit values for Boolean props. eslint:
react/jsx-boolean-value// bad (implicit true)<Hellopersonal/>;// super-bad (string value)<Hellopersonal="true"/>;// good (explicit)<Hellopersonal={true}/>;
Declare every prop in
propTypes. If you use a value onthis.propsanywhere in your component, it should be listed inpropTypes.// bad (missing propType declaration)exportdefaultReact.createClass({render(){return<div>Hello {this.props.name}</div>;}});// good (all props declared)exportdefaultReact.createClass({propTypes: {name: React.PropTypes.string,children: React.PropTypes.node},render(){const{children, name}=this.props;return(<div> Hello {name}{children}</div>);}});
Wrap JSX tags in parentheses when they span more than one line. eslint:
react/wrap-multilines// badrender(){return<MyComponentclassName="long body"foo="bar"><MyChild/></MyComponent>;}// goodrender(){return(<MyComponentclassName="long body"foo="bar"><MyChild/></MyComponent>);}// good, when single linerender(){constbody=<div>hello</div>;return<MyComponent>{body}</MyComponent>;}
Always self-close tags that have no children. eslint:
react/self-closing-comp// bad<FooclassName="stuff"></Foo>// good<FooclassName="stuff"/>
If your component has multi-line properties, close its tag on a new line. eslint:
react/jsx-closing-bracket-location// bad<Foobar="bar"baz="baz"/>// good<Foobar="bar"baz="baz"/>
Use arrow functions to close over local variables.
functionItemList(props){return(<ul>{props.items.map((item,index)=>(<Itemkey={item.key}onClick={()=>doSomethingWith(item.name,index)}/>))}</ul>);}
Bind event handlers for the render method in the constructor. eslint:
react/jsx-no-bind
Why? A bind call in the render path creates a brand new function on every single render.
```javascript
// bad
class extends React.Component {
onClickDiv() {
// do stuff
}
render() {
return <div onClick={this.onClickDiv.bind(this)} />
}
}
// good
class extends React.Component {
constructor(props) {
super(props);
this.onClickDiv = this.onClickDiv.bind(this);
}
onClickDiv() {
// do stuff
}
render() {
return <div onClick={this.onClickDiv} />
}
}
```
Do not use underscore prefix for internal methods of a React component.
// badReact.createClass({_onClickSubmit(){// do stuff},// other stuff});// goodclassextendsReact.Component{onClickSubmit(){// do stuff}// other stuff}
- Ordering for
class extends React.Component:
constructor- optional
staticmethods getChildContextcomponentWillMountcomponentDidMountcomponentWillReceivePropsshouldComponentUpdatecomponentWillUpdatecomponentDidUpdatecomponentWillUnmount- Getters, setters, event handlers, helper methods, etc.
- Optional render methods like
renderNavigation()orrenderProfilePicture() render
How to define
propTypes,defaultProps,contextTypes, etc...importReact,{PropTypes}from'react';constpropTypes={id: PropTypes.number.isRequired,url: PropTypes.string.isRequired,text: PropTypes.string,};constdefaultProps={text: 'Hello World',};classLinkextendsReact.Component{staticmethodsAreOk(){returntrue;}render(){return<ahref={this.props.url}data-id={this.props.id}>{this.props.text}</a>}}Link.propTypes=propTypes;Link.defaultProps=defaultProps;exportdefaultLink;
Ordering for
React.createClass: eslint:react/sort-comp
displayNamemixinspropTypescontextTypeschildContextTypesstaticsdefaultPropsgetDefaultPropsgetInitialStategetChildContextcomponentWillMountcomponentDidMountcomponentWillReceivePropsshouldComponentUpdatecomponentWillUpdatecomponentDidUpdatecomponentWillUnmount- Getters, setters, event handlers, helper methods, etc.
- Optional render methods like
renderNavigation()orrenderProfilePicture() render
- Do not use
isMounted. eslint:react/no-is-mounted
Why?
isMountedis an anti-pattern, is not available when using ES6 classes, and is on its way to being officially deprecated.
eslint rules: react/no-is-mounted.
Don't
setStateincomponentDidMountUpdating the state after a component mount will trigger a second render() call and can lead to property/layout thrashing. This does not apply to
setStatein event handlers added here.If you need to do something to change state here, use a function prop that can change state at the top level and pass new data down accordingly.
Don't
setStateincomponentDidUpdateUpdating the state after a component update will trigger a second render() call and can lead to property/layout thrashing.
If you need to do something to change state here, use a function prop that can change state at the top level and pass new data down accordingly.
These are guidelines more than rules, and they will likely be more controversial than those listed above. This is where we collaborate :)
Destructure
propsandstatevariables at the top ofrender()Instead of spreading your state access all over the place, destructure everything at the top of
renderto make it obvious which data fields are used.// badexportdefaultReact.createClass({ ... render(){return(<div>{/* access spread throughout component */} Hello {this.props.name}{/* stateful helpers... */}{this.renderHelper()}{/* transfers props that are intended for this component */}<CustomComponent{...this.props}/></div>);}});// goodexportdefaultReact.createClass({] ... render(){{/* all data in one place */}const{children, name, ...other}=this.props;return(<div> Hello {name}{/* stateless helper functions */}{this.renderHelper(children)}{/* only necessary props transferred */}<CustomComponent{...other}/></div>);}});
This has the additional benefit of:
- Keeping your helper methods stateless by passing in values (see below)
- Enabling props transfer using
...for unused values without triggering warnings for unused vars in render
Keep your helper methods stateless
Prefer to pass props/state values into your helper methods instead of access via
thisin the method body. This enables you to extract them to helper objects without refactoring, as well as easily write automated tests without needing to setup a complete stateful component.// discouraged (stateful method, harder to test and extract/refactor)exportdefaultReact.createClass({ ... render(){return(<div>{this.renderHelper()}</div>);},renderHelper(){return`Custom value: ${someRenderingLogic(this.props.value)}`;}});// preferred (stateless function, easy to test and extract/refactor)exportdefaultReact.createClass({] ... render(){const{value}=this.props;return(<div>{this.renderHelper(value)}</div>);},renderHelper(value){return`Custom value: ${someRenderingLogic(value)}`;}});