Skip to content

Instantly share code, notes, and snippets.

@jeffsoup
Created March 30, 2017 18:23
Show Gist options
  • Save jeffsoup/8f6fa21ffa6f58060adfde8551cf1a42 to your computer and use it in GitHub Desktop.
Save jeffsoup/8f6fa21ffa6f58060adfde8551cf1a42 to your computer and use it in GitHub Desktop.

Revisions

  1. jeffsoup created this gist Mar 30, 2017.
    467 changes: 467 additions & 0 deletions Seasoned_Standards.md
    Original 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;
    ```