This style guide comes as an addition to Airbnb React/JSX Guide. Feel free to modify it to suit your project's needs.
- Separate folder per UI component
- Prefer using functional components
- Use CSS Modules
- Use higher-order components
- Place each major UI component along with its resources in a separate folder
This will make it easier to find related resources for any particular UI element (CSS, images, unit tests, localization files etc.). Removing such components during refactorings should also be easy. - Avoid having CSS, images and other resource files shared between multiple components.
This will make your code more maintainable, easy to refactor. - Add
package.json
file into each component's folder.
This will allow to easily reference such components from other places in your code.
Useimport Nav from '../Navigation'
instead ofimport Nav from '../Navigation/Navigation.js'
/components/Navigation/icon.svg
/components/Navigation/Navigation.css
/components/Navigation/Navigation.js
/components/Navigation/Navigation.test.js
/components/Navigation/Navigation.ru-RU.css
/components/Navigation/package.json
// components/Navigation/package.json
{
"name:": "Navigation",
"main": "./Navigation.js"
}
For more information google for component-based UI development.
- Prefer using stateless functional components whenever possible.
Components that don't use state are better to be written as simple pure functions.
// Bad
class Navigation extends Component {
static propTypes = { items: PropTypes.array.isRequired };
render() {
return <nav><ul>{this.props.items.map(x => <li>{x.text}</li>)}</ul></nav>;
}
}
// Better
function Navigation({ items }) {
return (
<nav><ul>{items.map(x => <li>{x.text}</li>)}</ul></nav>;
);
}
Navigation.propTypes = { items: PropTypes.array.isRequired };
- Use CSS Modules
This will allow using short CSS class names and at the same time avoid conflicts. - Keep CSS simple and declarative. Avoid loops, mixins etc.
- Feel free to use variables in CSS via precss plugin for PostCSS
- Prefer CSS class selectors instead of element and
id
selectors (see BEM) - Avoid nested CSS selectors (see BEM)
- When in doubt, use
.root { }
class name for the root elements of your components
// Navigation.scss
@import '../variables.scss';
.root {
width: 300px;
}
.items {
margin: 0;
padding: 0;
list-style-type: none;
text-align: center;
}
.item {
display: inline-block;
vertical-align: top;
}
.link {
display: block;
padding: 0 25px;
outline: 0;
border: 0;
color: $default-color;
text-decoration: none;
line-height: 25px;
transition: background-color .3s ease;
&,
.items:hover & {
background: $default-bg-color;
}
.selected,
.items:hover &:hover {
background: $active-bg-color;
}
}
// Navigation.js
import React, { PropTypes } from 'react';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import s from './Navigation.scss';
function Navigation() {
return (
<nav className={`${s.root} ${this.props.className}`}>
<ul className={s.items}>
<li className={`${s.item} ${s.selected}`}>
<a className={s.link} href="/products">Products</a>
</li>
<li className={s.item}>
<a className={s.link} href="/services">Services</a>
</li>
</ul>
</nav>
);
}
Navigation.propTypes = { className: PropTypes.string };
export default withStyles(Navigation, s);
- Use higher-order components (HOC) to extend existing React components.
Here is an example:
// withViewport.js
import React, { Component } from 'react';
import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
function withViewport(ComposedComponent) {
return class WithViewport extends Component {
state = {
viewport: canUseDOM ?
{width: window.innerWidth, height: window.innerHeight} :
{width: 1366, height: 768} // Default size for server-side rendering
};
componentDidMount() {
window.addEventListener('resize', this.handleResize);
window.addEventListener('orientationchange', this.handleResize);
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleResize);
window.removeEventListener('orientationchange', this.handleResize);
}
handleResize = () => {
let viewport = {width: window.innerWidth, height: window.innerHeight};
if (this.state.viewport.width !== viewport.width ||
this.state.viewport.height !== viewport.height) {
this.setState({ viewport });
}
};
render() {
return <ComposedComponent {...this.props} viewport={this.state.viewport}/>;
}
};
};
export default withViewport;
// MyComponent.js
import React from 'react';
import withViewport from './withViewport';
class MyComponent {
render() {
let { width, height } = this.props.viewport;
return <div>{`Viewport: ${width}x${height}`}</div>;
}
}
export default withViewport(MyComponent);