- Basic Rules
- Class vs
React.createClass
- Naming
- Relative vs Absolute Path
- Barrel
- Declaration
- Alignment
- Quotes
- Spacing
- Props
- Tags
- Ordering
- i18n
- Charts
- Boilerplate
- 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.
- 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.
// bad
import React from 'react';
const Card = React.createClass({
// ...
render() {
return(<div>HotSchedules</div>)
}
})
// good
class Card extends Component {
// ...
render() {
return(<div>HotSchedules</div>)
}
}
- Always add spaces when importing member modules
// bad
import React, {Component, PropTypes} from 'react';
// good
import React, { Component, PropTypes } from 'react';
- Extensions:
- Use
.js
extension for all JavaScript Files, including React components. - Use
.scss
extension for all sass/scss files.
- Use
- 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
- Use PascalCase for directory names e.g.,
- Reference Naming:
- Use PascalCase for React Component and camelCase for their instances
// bad
import certificationCard from 'component/CeritficationCard';
const CertificationCard = <CertificationCard/>
// good
import CertificationCard from 'component/CeritficationCard';
const certificationCard = <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
- Use
- Resolve the paths to the root directories in
webpack.config.js
file.
resolve: {
extensions: ['', '.js', '.jsx', '.es6', '.scss', '.css'],
root: [
path.resolve('./img'),
path.resolve('./js'),
path.resolve('./scss')
]
}
- Use relative path to import components
// bad
import CertificationCard from '../../component/CeritficationCard';
// good
import CertificationCard from 'component/CeritficationCard';
- Rollup exports from several modules into a single module
// 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';
- Do not use
displayName
for naming component. Instead, name the component by refernece.
// bad
export default React.createClass({
displayName : 'CertificationCard',
// ...
})
// good
export default class CertificationCard extends Component {
// ...
}
- Wrap JSX in parentheses when they span more than one line
// bad
render() {
return <CertificationCard>
<Image />
</CertificationCard>
}
// good
render() {
return(
<CertificationCard>
<Image />
</CertificationCard>
);
}
- Follow these alignment styles for JSX syntax.
// bad
<CertificationCard title='Food Handler' expired={false}/>
<CertificationCard title='Food Handler'
expired={false}/>
// good
<CertificationCard
title='Food Handler'
expired={false}
/>
- Always use single qoutes
// bad
<CertificationCard
title="Food Handler"
/>
// good
<CertificationCard
title='Food Handler'
/>
- Always use a single splace in your self-closing tag.
// bad
<CertificationCard/>
// good
<CertificationCard />
- Do not pad curly braces with spaces.
// bad
<CertificationCard
expired={ false }
/>
// good
<CertificationCard
expired={false}
/>
- Always use camelCase for prop names.
// bad
<CertificationCard
certification_type='Food Handler'
/>
// good
<CertificationCard
certificationType='Food Handler'
/>
- Always include an
alt
prop on<img>
tags. If the image is presentational,alt
can be an empty string or the<img>
must haverole="presentation"
// bad
<img src="certificate.jpg" />
// good
<img src="certificate.jpg" alt="" />
<img src="certificate.jpg" alt="My food handler certificate" />
<img src="certificate.jpg" role="presentation" />
- Always self-close tags that have no children.
// bad
<CertificationCard></CertificationCard>
// good
<CertificationCard />
Ordering for class extends React.Component
:
constructor
getChildContext
componentWillMount
componentDidMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
componentDidUpdate
componentWillUnmount
render
- getter methods for
render
likegetCertificateContent()
- clickHandlers or eventHandlers like
onClickSubmit()
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.
const Store = ... // redux store
ReactDOM.render(
<Provider store={Store}>
<I18nProvider textResourcesUrl={'/hs/rest/punchrecords/textresources'}>
...
</I18nProvider>
</Provider>
, document.getElementById('root')
);
Rendering a localized string is now as simple as using I18nString
:
<I18nString bundleName={'staff.editPunchRecords'} string={'viewAll'} />
// or, for parameterized strings:
<I18nString bundleName={'staff.editPunchRecords'} string={'breakConfig'} params={['Foo', 'Bar']}/>
Now a <span>
will be rendered containing the translated string.
Localizing dates, times, numbers, and currencies still requires using bare
hsi18nutils
and/or Moment.
breakStart = Moment.parseZone(b.breakStart.iso8601).format(i18n.getTimeFormat(hsi18nutils.dtf.stf))
Use Victory Chart by FormidableLabs for simple charts: https://github.com/FormidableLabs/victory-chart
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.
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).
import React, { PropTypes } from 'react';
const Subpanel = (props) => {
return (
<div className='subpanel clearfix' key={`subpanel ${props.id}`}>
<div className='subpanel-top' key={`subpanel-top ${props.id}`}>
<div className='subpanel-title' key={`subpanel-title ${props.id}`}>
{props.title}
</div>
<div className='subprops-top-right' key={`subpanel-top-right ${props.id}`}>
{props.topRightText}
</div>
</div>
<div className='subpanel-bottom' key={`subpanel-bottom ${props.id}`}>
<div className='subpanel-bottom-left' key={`subpanel-bottom-left ${props.id}`}>
{props.bottomLeftText}
</div>
<div className='subpanel-bottom-right' key={`subpanel-bottom-right ${props.id}`}>
{props.bottomRightText}
</div>
</div>
</div>
);
};
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;
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.
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 (
<BaseCard
number={this.props.eventsCard.total}
title={this.formatTitle()}
subPanels={[]}
url='/hs/menuParser.hs?screen=dlb&sub_heading=eventCalendar'
/>
);
}
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;
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;