React ⚡️

Hero image for React ⚡️

Formatting

  • Use soft-tabs with a two spaces indent.
  • Limit each line of code to fewer than 130 characters.
  • End each file with a newline.
  • Enforce the closing bracket location for JSX multiline elements (learn more)

    <Hello
      lastName="Smith"
      firstName="John" />;
    
    <Hello firstName="John" lastName="Smith" />;
    
    <Hello
      firstName="John"
      lastName="Smith"
    />;
    
    {showButton &&
      <Button />
    }
    
    {showButton && (
      <Button />
    )}
    
    {showButton && <Button />}
    

Naming

  • Use jsx extensions for components, screens etc.
  • Use PascalCase for files with the jsx extension:

    components/
    ├── Button/
    │   ├── index.jsx
    │   ├── index.test.jsx
    screens/
    ├── Home/
    │   ├── index.jsx
    │   ├── index.test.jsx
    

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

  • Use camelCase for all other files with a js extension:

    lib/
    ├── requestManager.js
    ├── requestManager.test.js
    helpers/
    ├── userHelper.js
    ├── userHelper.test.js
    
  • Use camelCase for variables and functions:

    let form_params = {};
    
    function FetchParams() {
      return {};
    }
    
    let formParams = {};
    
    function fetchParams() {
      return {};
    }
    
  • Use PascalCase for classes:

    class product_form {}
    class productForm {}
    
    class ProductForm {}
    
  • Use SCREAMING_SNAKE_CASE for constants:

    const fetchLimit = 25;
    
    const FETCH_LIMIT = 25;
    

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

  • Use PascalCase for components and camelCase for their instances:

    import menuCard from './MenuCard';
    
    import MenuCard from './MenuCard';
    
    const MenuIten = <MenuCard />;
    
    const menuItem = <MenuCard />;
    
  • Use camelCase for prop names and PascalCase if the prop value is a React component:

    <Foo
      UserName='hello'
      phone_number={ 12345678 }
    />
    
    <Foo
      userName='hello'
      phoneNumber={ 12345678 }
      Component={ SomeComponent }
    />
    

Props

  • Omit the value of the prop when it is explicitly true:

    <Foo
      hidden={true}
    />
    
    <Foo hidden />
    
  • 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' attribute:

    <img src='hello.jpg' />
    
    <img src='hello.jpg' alt='Me waving hello' />
    
    <img src='hello.jpg' alt='' />
    
    <img src='hello.jpg' role='presentation' />
    
  • Avoid using an array index as key prop, prefer a stable ID:

    { todos.map((todo, index) =>
      <Todo
        {...todo}
        key={index}
      />
    ) }
    
    { todos.map(todo => (
      <Todo
        {...todo}
        key={todo.id}
      />
    )) }
    

Tags

  • For single components (having no children), always use self-close tags.

    <Foo variant="noChildren"></Foo>
    
    <Foo variant="noChildren" />
    
  • For components with multi-line properties, close the tag on a new line

    <Foo
      bar="bar"
      foo="foo" />
    
    <Foo
      bar="bar"
      foo="foo"
    />
    

Functions

  • Use arrow functions to close over local variables.

    function TaskList(props) {
      return (
        <ul>
          {props.tasks.map((task, index) => (
            <Item
              key={task.key}
              onClick={() => someFunc(task.name, index)}
            />
          ))}
        </ul>
      );
    }
    
  • In the constructor, Bind event handlers for the render method, Otherwise it creates a whole new function on every single render and reduces performance.

    class extends React.Component {
      onClickDiv() {
        ....
      }
    
      render() {
        return <div onClick={this.onClickDiv.bind(this)} />;
      }
    }
    
    class extends React.Component {
      constructor(props) {
        super(props);
    
        this.onClickDiv = this.onClickDiv.bind(this);
      }
    
      onClickDiv() {
        ....
      }
    
      render() {
        return <div onClick={this.onClickDiv} />;
      }
    }
    
  • Be sure to return a value in render methods.

    render() {
      (<div />);
    }
    
    render() {
      return (<div />);
    }
    

Project Structure

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

adapters/
├── product.js
├── product.test.js
components/
├── Product/
│   ├── index.jsx
│   ├── index.test.jsx
├── ProductList/
│   ├── index.jsx
│   ├── index.test.jsx
constants/
├── product.js
contexts/
├── auth.js
├── auth.test.js
helpers/
├── formatProductDescription.js
├── formatProductDescription.test.js
reducers/
├── product.js
├── product.test.js
screens/
├── ProductDetails/
│   ├── index.jsx
│   ├── index.test.jsx
services/
├── googleMap.js
├── googleMap.test.js
tests/
├── helpers.js
  • adapters/: the classes in charge of making network and/or async tasks. One file per API resource is usually created.

  • components/: the stateless and reusable React components.

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

  • contexts/: creating contexts and context providers.

  • helpers/: any utilities used in the project.

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

  • screens/: the page-specific components which are in charge of rendering all other stateless components to build any specific page.

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

  • tests/: any tests helpers and generic component wrappers.

Components / Screens

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

components/
├── Button.jsx
├── Button.test.jsx
screens/
├── Product.jsx
├── Product.test.jsx
components/
├── Button/
│   ├── index.jsx
│   ├── index.test.jsx
screens/
├── ProductDetails/
│   ├── index.jsx
│   ├── index.test.jsx

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

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

Component Props

React PropTypes are a critical part of creating reusable 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,
    
      /**
      * 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
    
      /**
       * Click event handler.
       **/
      onClick: PropTypes.func,
    }).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
│   ├── index.test.jsx
│   ├── propTypes.js
├── Feed/
│   ├── index.jsx
│   ├── index.test.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,
  ...
});
  • Do not use accessKey on elements. eslint: jsx-a11y/no-access-key

Reason: Inconsistencies between keyboard shortcuts and keyboard commands used by people using screen readers and keyboards complicate accessibility.

<div accessKey="h" />
<div />

Containers

Use a single file structure:

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

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

Hooks

  • ESLint plugin for react hook: eslint-plugin-react-hooks

    This plugin is developed by the React team. It is simple and consists only of a couple of rules

  • Avoid calling hooks inside loops, conditions, and nested functions

    This would ensure that Hooks are being called in the same order each time a component renders. That allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls

    if (name !== '') {
      useEffect(function persistForm() {
        localStorage.setItem('formData', name);
      }
    });
    
    useEffect(function persistForm() {
      if (name !== '') {
        localStorage.setItem('formData', name);
      }
    });
    
  • Every value referenced inside the effect function should also appear in the dependencies array

    For example, the following userInfo component will trigger the exhaustive-deps warning because the userId variable gets referenced inside the useEffect but it is not passed in the dependencies array

    function ProductInfo({productId}) {
      const [product, setProduct] = useState(null)
      useEffect(() => {
        getProduct(productId).then(product => seProduct(product))
      }, []) // no productId here
    
      return <div>Product detail:</div>
    }
    

Tests

Test files are located next to the files being tested. This makes code review more natural as the tests changes will be right next to their related code changes. The name for tests files should follow the {file-name}.test.{extension-name} convention. For example, an index.jsx code file would have a test file nammed index.test.jsx.

The tests folder contains the needed helpers and/or component wrappers to support tests.

components/
├── Product/
│   ├── index.jsx
│   ├── index.test.jsx
│   ├── product-item.jsx
│   ├── product-item.test.jsx
tests/
├── helpers.js

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