React ⚡️

Hero image for React ⚡️

Formatting

  • Use soft-tabs with a two space indent.
  • Limit each line of code to fewer than 130 characters.
  • Use camel-case for variables and functions:
// Bad
let form_params = {};
function FetchParams() { 
  return {};
}

// Good
let formParams = {};
function fetchParams() { 
  return {};
}
  • Use title-case for classes:
// Bad
class product_form {}
class productForm {}

// Good
class ProductForm {}
  • Use capital-case for constants:
// Bad
const fetchLimit = 25;

// Good
const FETCH_LIMIT = 25;

This formatting is somewhat inherited from Ruby but is actually widely accepted in the JS community.

Many of the React formatting conventions derived from the Javascript conventions.

Naming

  • Use jsx extensions for components, containers and screens
  • Use title-case for files with the jsx extension:
components/
├── Button/
│   ├── index.jsx
containers/
├── Home.jsx
screens/
├── Home/
│   ├── index.jsx

index.jsx is the only exception to this rule as it represent the folder root. This filename can also be omitted in import statements: import Button from '../Button/'.

  • Use camel-case for all other files with a js extension:
lib/
├── requestManager.js
helpers/
├── userHelper.js

Project Structure

After working on several React projects, we settled down on the following file organization (this structure is closed to the general JS application structure):

actions/
├── product.js
adapters/
├── product.js
components/
├── Product/
│   ├── index.jsx
├── ProductList/
│   ├── index.jsx
constants/
├── product.js
containers/
├── ProductDetails.jsx
helpers/
├── formatProductDescription.js
reducers/
├── product.js
screens/
├── ProductDetails/
│   ├── index.jsx
services/
├── googleMap.js
store/
├── store.js
  • actions/: the Redux actions creators.

  • adapters/: the classes in charge of making network and/or async tasks. We usually create one file per API resource.

  • components/: the stateless and re-usable React components.

  • constants/: the actions types under this directory. The organization of this directory should match the folder structure of the actions.

  • containers/: the stateful React components. These React components are mounted by the react-router (i.e. used only once), map the Redux actions and state to props and pass them down to the screens.

  • helpers/: Any utilities used in the project.

  • reducers/: the Redux reducers. The organization of this directory should match the folder structure of actions and constants.

  • screens/: the page specific components which are in charge of rendering all other stateless components to build any specific page. These components map one-to-one to those under containers.

  • services/: the service classes used in the project. By definition, these classes should encapsulate one single process of the app.

  • store/: the Redux store objects.

Components / Screens

Use a folder structure with an index file (and other files when required):

// Bad
components/
├── Button.jsx
screens/
├── Product.jsx

// Good
components/
├── Button/
│   ├── index.jsx
screens/
├── ProductDetails/
│   ├── index.jsx

This is both a future-proof measure and a mean to break down components into small meaningful modules:

components/
├── Button/
│   ├── index.jsx
│   ├── loading.jsx
│   ├── propTypes.js
screens/
├── ProductDetails/
│   ├── index.jsx
│   ├── header.jsx
│   ├── gallery.jsx

Containers

Use a single file structure:

// Bad
containers/
├── ProductDetails/
│   ├── index.jsx

// Good
containers/
├── ProductDetails.jsx

Since very logic should be placed in containers, a single file structure forces to keep the complexity to a minimum.

Component Props

React PropTypes are a critical part of creating re-usable components with a clear API.

  • Use the right PropType that matches the expected data type for each prop:
Button.propTypes = {
  /**
   * Holds the text to display in the button.
   **/
  text: PropTypes.string,

  /**
   * Disabled state
   **/
  disabled: PropTypes.bool,

  /**
   * Click event handler.
   **/
  onClick: PropTypes.func,
  
  /**
  * Select the style type.
  **/
  styleType: PropTypes.oneOf(['default', 'primary', 'secondary'])
};

  • Define which prop is required:
Button.propTypes = {
  /**
   * Holds the text to display in the button.
   **/
  text: PropTypes.string.isRequired,

  /**
   * Disabled state
   **/
  disabled: PropTypes.bool
};

  • Use PropTypes.shape to define complex props:
/**
* Available action creators.
**/
actions: PropTypes.shape({
    /**
     * When a ticket is selected.
     **/
    pickTicket: PropTypes.func.isRequired,
    
    /**
     * When a ticket is unselected.
     **/
    unpickTicket: PropTypes.func.isRequired
}).isRequired

/**
 * Holds the search store.
 * */
search: PropTypes.shape({
    /**
     * Holds the origin city.
     **/
    origin: PropTypes.string.isRequired,
    
    /**
     * Holds the destination city.
     **/
    destination: PPropTypes.string.isRequired
}).isRequired

The definition of PropTypes allows for complex type structure between components:

components/
├── Product/
│   ├── index.jsx
│   ├── propTypes.js
├── Feed/
│   ├── index.jsx
│   ├── propTypes.js

In Feed/propTypes.js:

import PropTypes from 'prop-types';
import productPropType from '../Product/propTypes';

export const feedPropType = PropTypes.shape({
  products: PropTypes.arrayOf(productPropType).isRequired,
  ...
});

Redux

Depending on the complexity of the project, we follow two types of architecture:

1. Entity-based

products:
  isFetching: false
  fetchSuccess: false
  fetchFailure: false
  products: []
reviews:
  isFetching: false
  fetchSuccess: false
  fetchFailure: false
  reviews: []

This structure is suited for well for small / not complex project with few screens and entities.

This structure is the most straightforward and does not require much overhead in terms of planning as it usually maps to the API entities. It also have the advantage to be a flat structure with only two levels.

Known Concerns

  • Overhead to pass down props to screens.

As the entities are stored in a flat structure, it’s necessary to filtering specific entities from a global store before passing them down to screens or components. For examples, in an e-commerce application, all products would be stored in products and product reviews for all products in reviews. In order to show the product details, it would be necessary to filter the list of reviews by the product ID.

  • Handling of data and refreshing specific entities is hard in large projects.

2. Screen-based

productDetails:
  products:
    isFetching:
    fetchSuccess:
    fetchFailure:
    products: []
  reviews:
    isFetching:
    fetchSuccess:
    fetchFailure:
    reviews: []

This structure is recommend well for large / complex project with many screens and entities.

This structure require some overhead in terms of planning as it adds more complexity to the Redux store. Compared to the entity-based structure, it provides the following advantages:

  • There is no overhead of filtering entites from a global store and passing down to the components.
  • Clearing of the store and refreshing a specific entity is quite simple.
  • It allows for caching store on a page level which plays quite well for offline supports.

Known Concerns

  • Duplication of the store actions and reducers.

If the product reviews were displayed in another area of the application then it would require to re-use / duplicate the actions and reducers for product reviews. While this might seem counter-productive, it actually provides more flexibility in large projects as the handling of the same data varies from one screen to another