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.

HotSchedules React/JSX Style Guide

Table of Content

  1. Basic Rules
  2. Class vs React.createClass
  3. Naming
  4. Relative vs Absolute Path
  5. Barrel
  6. Declaration
  7. Alignment
  8. Quotes
  9. Spacing
  10. Props
  11. Tags
  12. Ordering
  13. i18n
  14. Charts
  15. 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

// 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
// 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.
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';

Barrel

  • 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';

Declaration

  • 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 {
    // ...
}

Parentheses

  • Wrap JSX in parentheses when they span more than one line
// bad
render() {
    return <CertificationCard>
        <Image />
        </CertificationCard>
}
// good
render() {
    return(
        <CertificationCard>
            <Image />
        </CertificationCard>
    );
}

Alignment

  • 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}
/>

Quotes

  • Always use single qoutes
// bad
<CertificationCard
    title="Food Handler"
/>

// good
<CertificationCard
    title='Food Handler'
/>

Spacing

  • 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}
/>

Props

  • 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 have role="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" />

Tags

  • Always self-close tags that have no children.
// bad
<CertificationCard></CertificationCard>

// good
<CertificationCard />

Ordering

Ordering for class extends React.Component:

  1. constructor
  2. getChildContext
  3. componentWillMount
  4. componentDidMount
  5. componentWillReceiveProps
  6. shouldComponentUpdate
  7. componentWillUpdate
  8. componentDidUpdate
  9. componentWillUnmount
  10. render
  11. getter methods for render like getCertificateContent()
  12. 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.

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))

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).

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.

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

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;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment