# HotSchedules React/JSX Style Guide
## Table of Content
1. [Basic Rules](#basic-rules)
1. [Class vs `React.createClass`](#class-vs-reactcreateclass)
1. [Naming](#naming)
1. [Relative vs Absolute Path](#relative-vs-absolute-path)
1. [Barrel](#barrel)
1. [Declaration](#declaration)
1. [Alignment](#alignment)
1. [Quotes](#quotes)
1. [Spacing](#spacing)
1. [Props](#props)
1. [Tags](#tags)
1. [Ordering](#ordering)
1. [i18n](#i18n)
1. [Charts](#charts)
1. [Boilerplate](#boilerplate)
## Tabs vs Spaces
- Always use tabs for indentation, Why?
-- are consistent, they are only used for indentation.
-- are customizable, you can specify the width in your editor.
-- are more productive, why type 4 spaces when you can hit 1 tab.
-- allow mistakes to be more noticable.
## Basic Rules
- Only include one React component per file.
- Always use JSX syntax.
- Do not use `React.createClass`
- Always use semi-colon after a statement
- Do NOT use isMounted. *isMounted is an anti-pattern, is not available when using ES6 classes, and is on its way being officially deprecated.*
# Class vs `React.createClass`
```js
// bad
import React from 'react';
const Card = React.createClass({
// ...
render() {
return(
HotSchedules
)
}
})
// good
class Card extends Component {
// ...
render() {
return(HotSchedules
)
}
}
```
- Always add spaces when importing member modules
```
// bad
import React, {Component, PropTypes} from 'react';
// good
import React, { Component, PropTypes } from 'react';
```
## Naming
- **Extensions**:
- Use `.js` extension for all JavaScript Files, including React components.
- Use `.scss` extension for all sass/scss files.
- **Components**:
- Use PascalCase for directory names e.g., `CertificationCard`
- Use filename as the component for example CertificationCard.js should have a reference name of CertificationCard. However for root components of a directory use index.js as the filename and use the directory name as the component name
- **Reference Naming**:
- Use PascalCase for React Component and camelCase for their instances
```js
// bad
import certificationCard from 'component/CeritficationCard';
const CertificationCard =
// good
import CertificationCard from 'component/CeritficationCard';
const certificationCard =
```
- **File Naming**: Each component directory must contain four files. (component file, styles, specs and stories)
- Use `CertificationCard.js` for the component file.
- Use `CertificationCard.scss` for styles file.
- Use `CertificationCard.spec.js` for the unit tests file
- Use `CertificationCard.story.js` for the stories file
## Relative vs Absolute Path
- Resolve the paths to the root directories in `webpack.config.js` file.
```js
resolve: {
extensions: ['', '.js', '.jsx', '.es6', '.scss', '.css'],
root: [
path.resolve('./img'),
path.resolve('./js'),
path.resolve('./scss')
]
}
```
- Use relative path to import components
```js
// bad
import CertificationCard from '../../component/CeritficationCard';
// good
import CertificationCard from 'component/CeritficationCard';
```
## Barrel
- Rollup exports from several modules into a single module
```js
// index.js inside PunchModal folder
export { default as EditPunchContainer } from './EditPunchContainer';
export { default as DeletePunch } from './DeletePunch';
export { default as PunchHistoryContainer } from './PunchHistoryContainer';
// Component that uses PunchModal
import PunchModal from 'components/PunchModal';
```
## Declaration
- Do not use `displayName` for naming component. Instead, name the component by refernece.
```js
// bad
export default React.createClass({
displayName : 'CertificationCard',
// ...
})
// good
export default class CertificationCard extends Component {
// ...
}
```
## Parentheses
- Wrap JSX in parentheses when they span more than one line
```js
// bad
render() {
return
}
// good
render() {
return(
);
}
```
## Alignment
- Follow these alignment styles for JSX syntax.
```html
// bad
// good
```
## Quotes
- Always use single qoutes
```html
// bad
// good
```
## Spacing
- Always use a single splace in your self-closing tag.
```html
// bad
// good
```
- Do not pad curly braces with spaces.
```html
// bad
// good
```
## Props
- Always use camelCase for prop names.
```html
// bad
// good
```
- Always include an `alt` prop on `
` tags. If the image is presentational, `alt` can be an empty string or the `
` must have `role="presentation"`
```html
// bad
// good
```
## Tags
- Always self-close tags that have no children.
```html
// bad
// good
```
## Ordering
Ordering for `class extends React.Component`:
1. `constructor`
1. `getChildContext`
1. `componentWillMount`
1. `componentDidMount`
1. `componentWillReceiveProps`
1. `shouldComponentUpdate`
1. `componentWillUpdate`
1. `componentDidUpdate`
1. `componentWillUnmount`
1. `render`
1. getter methods for `render` like `getCertificateContent()`
1. clickHandlers or eventHandlers like `onClickSubmit()`
## i18n
Wrap your entire application in the `I18nProvider` component. This will provide
the necessary context to the `I18nString` component so it renders strings
correctly.
Any new project will need to provide an HTTP endpoint which will provide a JSON
structure usable by `hsi18nutils.js` component.
```jsx
const Store = ... // redux store
ReactDOM.render(
...
, document.getElementById('root')
);
```
Rendering a localized string is now as simple as using `I18nString`:
```jsx
// or, for parameterized strings:
```
Now a `` will be rendered containing the translated string.
Localizing dates, times, numbers, and currencies still requires using bare
`hsi18nutils` and/or Moment.
```js
breakStart = Moment.parseZone(b.breakStart.iso8601).format(i18n.getTimeFormat(hsi18nutils.dtf.stf))
```
## Charts
Use Victory Chart by FormidableLabs for simple charts:
https://github.com/FormidableLabs/victory-chart
## Boilerplate
We have 2 different types of components. A class component, used when the
component should be a "controlled" component or a smart component and a
stateless function, used when the component should be presentational layer
logic only.
### Function Components
If your component is a pure function of its `props` and does not need React
lifecycle methods, make it a function component as in `Subpanel` below. If your
component needs to subscribe to the Redux store or needs to use component
state (as in `this.state`), read the section below about
[class components](#class components).
```jsx
import React, { PropTypes } from 'react';
const Subpanel = (props) => {
return (
{props.title}
{props.topRightText}
{props.bottomLeftText}
{props.bottomRightText}
);
};
Subpanel.propTypes = {
id:PropTypes.number.isRequired,
title: PropTypes.string.isRequired,
topRightText: PropTypes.node,
bottomLeftText: PropTypes.node,
bottomRightText: PropTypes.node
};
Subpanel.defaultProps = {
id: 1,
title: ' ',
topRightText: ' ',
bottomLeftText: ' ',
bottomRightText: ' '
};
export default Subpanel;
```
### Class Components
A Class Component handles complicated interactions, typically with the server.
It is otherwise known as a smart component or a controlled component.
Typically, it will have redux features in it, connecting it to a store. For
example, the class component below uses mapDisatchToProps to call the server
using getEventsCard() and it uses mapStateToProps to read the response from the
server. Notice the anonymouse stateless function above is a strict render
without any complicated server logic, where as this class component talks to
the server.
```js
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { getEventsCard } from '../../actions/Api';
import BaseCard from '../BaseCard/BaseCard';
class EventsCard extends Component {
constructor(props) {
super(props);
this.state = {
eventsCard: {
total: 0
}
};
}
componentDidMount() {
this.props.getEventsCard();
}
render() {
return (
);
}
formatTitle() {
if (this.props.eventsCard.total <= 0) {
return 'No Events on This Week\'s Calendar';
} else {
return 'This Week\'s Events Calendar';
}
}
}
const mapStateToProps = (state) => {
return {
eventsCard: state.eventsCard
};
};
const mapDispatchToProps= (dispatch) => {
return bindActionCreators({
getEventsCard: getEventsCard
}, dispatch);
};
EventsCard.propTypes = {
getEventsCard: React.PropTypes.func,
eventsCard: React.PropTypes.object
};
const EventsCardContainer = connect(
mapStateToProps,
mapDispatchToProps
)(EventsCard);
export default EventsCardContainer;
```
### Store
```js
import Reducers from './reducers';
import Thunk from 'redux-thunk';
let middlewares = [applyMiddleware(Thunk)];
const storeFile = 'hs-store';
if (process.env.NODE_ENV === 'development') {
if (window.devToolsExtension) {
middlewares.push(window.devToolsExtension());
}
}
function configureStore(initialState) {
// Load the state from local storage
const persistedState = localStorage.getItem(storeFile) ? JSON.parse(localStorage.getItem(storeFile)) : {} ;
initialState = persistedState;
return compose(...middlewares)(createStore)(Reducers, initialState);
}
const store = configureStore();
// Store the state in local storage
store.subscribe(function(){
localStorage.setItem(storeFile, JSON.stringify(store.getState()));
});
export default store;
```