ReactJS Coding Convention

Common rules (for both ReactJS and React Native)

  1. Only use latest LTS version. 12 at the time we are saying.
  2. Install <code>node-gyp</code> as global package.

I. Coding syntax:

1. Variables must be named in camelCase style:

// OK
import reservationCard from './ReservationCard';

// Nope
import ReservationCard from './ReservationCard';

// Nope
const ReservationItem = <ReservationCard />;

// OK
const reservationItem = <ReservationCard />;

2. Follow StandardJS, strictly.

Why? With standardjs + lint-staged + snazzy, we can do code-linting and fixing without chunks of eslint-plugin packages.

3. Use named import/export instead of default export:

// Good
export class ClusteredMapView extends PureComponent {
  constructor (props) {
    super(props)
  }
}
// Bad
export default class ClusteredMapView extends PureComponent {
  constructor (props) {
    super(props)
  }
}

II. Code structure:

There are two ways to “correctly” make your code structure:

1. “Essential” way:

This is the common, minimal boilerplate but also fully-featured one. Basically it was created using create-react-app (CRA). Ejected or not, it will have something similar to this tree:

├── actions
├── api
├── assets
│   ├── fonts
│   ├── icons
│   ├── images
│   └── styles
├── common
├── components
│   ├── button
│   ├── header
│   │   ├── header-button
│   │   └── searchbox
│   ├── logo
│   └── swtich
├── reducers
├── routes
│   ├── authen
│   │   ├── products
│   │   ├── purchase-request
│   │   └── successful
│   └── guest
│       └── login
│           └── login-form
├── sagas
├── stores
└── utils

As you can see, we use Redux and Redux Saga. I am not a Redux maniac, but it’s so popular that people usually thought it was “essential” part of a React project.

But anyway, it’s easy to get on with it, and there is excellent documentation out there, here we go:

  • Actions: Contains Redux and react-router‘s actions. It also contains types.js which holds all action types, to be used with those in reducers too.

  • Api: Contains network api-related files. The root api factory is api.js, all header modifications, interceptors should be put here.

  • Assets: Contains external resource files, such as images, audio files, icons and so on.

  • Common: Contains global-wide used functions and components, such as localization, common color list, and pre-defined configurations. It is also configured as a sub-module. So you can use any part of it in a package named app-common like this:

import { Metrics } from 'app-common'
  • Components: Contains reusable components, some of which are not classified into any folder, which means they are used usually. The folder depth should not be larger than 1.

  • Reducers and Sagas: Reducers to be used by redux, and for side-effect processing (with powerful redux-saga library).

  • Routes: Contain root entries to app’s individual screens. Each screen is connected directly with the main Router. authen-routes and guest-routes are self-explained.

  • Stores: Contain a single redux‘s store. May change in the future.

  • Utils: Contains utility functions, like, unit converters, field data validator, field data formatter,…

This kind of structure assuming that you did not “eject” the project generated by CRA at all. So you can just:

npm run start

to start developing.
And of course, at anytime, you can npm run eject to extract configuration files, such as .babelrc, .eslintrc and Webpack config files.

The “veteran” way

This structure is also created using create-react-app. But ejecting it is a must to install and modify additional dependencies and components.

├── actions
├── api
├── assets
│   ├── fonts
│   ├── icons
│   ├── images
│   └── styles
├── common
├── components
├── reducers
├── routes
│   ├── authen
│   └── guest
├── sagas
├── stores
└── utils

Folder structure is almost the same with the first one. But this time we will extra packages:

  • standardjs for code linting and formatting.
  • snazzy (Conjunction with StandardJS)
  • lint-staged (Detect staged files and run tests / linting with it before going to next steps).
  • reselect (Memoized state for Redux, TL;DR: Redux performance enhancer).
  • Jest: For snapshot testing.
  • Enzyme: Component rendering test.
  • node-sass-chokidar: Replacement for node-sass (there are some rare issues on MacOS regarding CPU usages may rise up to 300%).

The “alter-ego” way

Another approach is removing Redux completely and use newly revamped Context API which is introduced upon release of React 16.3.

├── __tests__
├── __mocks__
├── config
├── api
├── assets
│   ├── fonts
│   ├── icons
│   ├── images
│   └── styles
├── common
├── components
├── contexts
├── routes
│   ├── authen
│   └── guest
└── utils

As you can see, stores, actions and reducers are gone, for the contexts to come. It is basically home for declaring… um… contexts, like this:

import React from 'react'

const UserContext = React.createContext()

export class UserProvider extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      user: null,
      updateUser: this.updateUser.bind(this)
    }
  }

  updateUser = (user) => {
    this.setState({ user })
  }

  render () {
    return (
      <UserContext.Provider value={this.state}>
        {this.props.children}
      </UserContext.Provider>
    )
  }
}

export function withUser (Component) {
  return class _ extends React.Component {
    render () {
      return (
        <UserContext.Consumer>
          {value => {
            return <Component {...this.props} {...value} />
          }}
        </UserContext.Consumer>
      )
    }
  }
}

To provide user object and updateUser method, we just need to wrap the exported component:

import React, { Component } from 'react'
import { withUser } from '../../contexts/user-context'

class _ extends Component {
  render () {
    ...
  }
}

export const ComponentWithUser = withUser(_)

For testing, we still use Jest – Mocha – Enzyme stack. Hence __tests__ and __mocks__ folders.

Last but not least, we will use StandardJS for code linting. Why standardjs? It’s simple, “just works” and requires very little setup, yet efficient. All you need to do is installing packages:

npm i standard snazzy husky lint-staged --save

And refer to package.json file for an example to setup properly. Instead of installing a bunch of eslint packages like this one below (ewww):

"eslint": "^3.19.0",
"eslint-config-airbnb": "^15.1.0",
"eslint-config-prettier": "^2.6.0",
"eslint-plugin-flowtype": "^2.37.0",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-jest": "^21.2.0",
"eslint-plugin-jsx-a11y": "5.0.1",
"eslint-plugin-prettier": "^2.3.1",
"eslint-plugin-react": "^7.4.0",
"eslint-plugin-react-native": "^3.1.0",

With standardjs successfully setup, we will have code files formatted like this:

import React, { Component } from 'react'
import { Card, Modal } from 'antd'

export class FavoriteDetail extends Component { // Camel-case
  render () { // Space after method name
    return (
      <Card // 2 spaces indent.
        bordered={false}
        title={`Data`}
        extra={<p className='detail' href='#'>Test</p>} // Prefer single-quote.
        className='card'
      >
        <p>Data</p>
      </Card>
    )
    // JSX close tag at new line.
  }
}

Server-side Rendering?

Okay so you want SSR instead of SPA solution up there. SSR, in some cases, is better (SEO, slightly faster page processing,… you name it). But it also increases server-side load, and involves more build setup, plus some disadvantages (e.g, localStorage), also some lifecycle hooks on components might be unusable.

With SSR, there is no option better than infamous Next.js framework. Anyway, the structure will become something like:

├── __tests__
├── __mocks__
├── api
├── common
│   └── localization
├── components
│   ├── common
│   ├── course
│   ├── favorites
│   ├── lesson
│   ├── login
│   ├── register
│   └── unit
├── contexts
├── hoc
├── pages
│   └── classes
├── static
│   ├── images
│   ├── styles
│   └── translations
└── utils

The important folder is pages, where the name of the file IS the name of the route.

Golden rules

I can’t stress these bullets enough, so please keep in mind:

  • Always. I repeat: Always use lower-case file name. Like: helpful-component.js, it will reduce the chance to encounter problems with syncing and git diff processing, as well as avoiding cross-platform development problems.
  • Keep the code clean and below 200 lines of code per file. Seriously.
  • Don’t use naked regex for validation in components. Write proper, meaningful-named functions for later re-uses.
  • Learn to use SCSS / Stylus. Those CSS pre-processing engines will help you a lot with component styling in the long run.
  • Use meaningful commit message. Don’t use eerie, dumb strings like “fix bug”, “testing”,… It hurts your co-workers and maintenance a lot.
  • If you are not sure about your function / components. Test it again, using unit testing or whatever you call it.

You may also like...

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.