Created
March 30, 2017 18:23
-
-
Save jeffsoup/8f6fa21ffa6f58060adfde8551cf1a42 to your computer and use it in GitHub Desktop.
Revisions
-
jeffsoup created this gist
Mar 30, 2017 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,467 @@ # 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(<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'; ``` ## 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 = <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 ## 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 <CertificationCard> <Image /> </CertificationCard> } // good render() { return( <CertificationCard> <Image /> </CertificationCard> ); } ``` ## Alignment - Follow these alignment styles for JSX syntax. ```html // bad <CertificationCard title='Food Handler' expired={false}/> <CertificationCard title='Food Handler' expired={false}/> // good <CertificationCard title='Food Handler' expired={false} /> ``` ## Quotes - Always use single qoutes ```html // bad <CertificationCard title="Food Handler" /> // good <CertificationCard title='Food Handler' /> ``` ## Spacing - Always use a single splace in your self-closing tag. ```html // bad <CertificationCard/> // good <CertificationCard /> ``` - Do not pad curly braces with spaces. ```html // bad <CertificationCard expired={ false } /> // good <CertificationCard expired={false} /> ``` ## Props - Always use camelCase for prop names. ```html // 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 have `role="presentation"` ```html // 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" /> ``` ## Tags - Always self-close tags that have no children. ```html // bad <CertificationCard></CertificationCard> // good <CertificationCard /> ``` ## 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( <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`: ```jsx <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. ```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 ( <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; ``` ### 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 ( <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; ``` ### 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; ```