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 new line.
  • 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 / tsx extensions for components, screens etc. and their tests.
  • Use PascalCase for files with the jsx / tsx extensions:

    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 or ts extension:

    lib/
    ├── requestManager.js
    ├── requestManager.test.js
    helpers/
    ├── userHelper.js
    ├── userHelper.test.js
    
  • Use camelCase for variables and functions that are not React components:

    const form_params = {};
    
    const FetchParams = () => {
      return {};
    }
    
    const formParams = {};
    
    const fetchParams = () => {
      return {};
    }
    
  • Use PascalCase for React component functions and classes:

    class product_form extends React.Component {}
    class productForm extends React.Component {}
    
    const productForm = () => (<></>);
    
    class ProductForm extends React.Component {}
    
    const 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';
    
    const dividerComponent = () => (<hr />);
    
    import MenuCard from './MenuCard';
    
    const DividerComponent = () => (<hr />);
    
    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 }
      ComponentInstance={<h1>Hi</h1>}
      component={ SomeComponent }
    />
    
    <Foo
      userName='hello'
      phoneNumber={ 12345678 }
      componentInstance={<h1>Hi</h1>}
      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 the key prop, prefer a stable ID:

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

Tags

  • For components having no children, always use a self-closed tag.

    <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 wrap a function call with local variables.

    const TaskList = (props) => (
      <ul>
        {props.tasks.map((task, index) => (
          <Item
            key={task.key}
            onClick={() => someFunc(task.name, index)}
          />
        ))}
      </ul>
    );
    

Prefer using function components over class components for the following reasons:

  • Readability: Easier to read and test
  • Simplicity: No need to understand some complex concepts like “this”, binding, constructors, and lifecycle methods.
  • Less code: Function components often do the same for less code.
  • Performance: Function components are less costly in terms of memory and processing power as they don’t have instances like class components.
  • Hooks: React Hooks, introduced in React 16.8, make it possible to take a function component and add state to it and/or lifecycle methods. Hooks make function components as powerful as class components.
  • The Future Direction: React’s future direction is geared more towards function components. For instance, Concurrent Mode, an experimental feature in React, works only with function components.

    class extends React.Component {
      constructor(props) {
        super(props);
    
        this.onClick = this.onClick.bind(this);
      }
    
      onClick() {
        // ...
      }
    
      render() {
        return <div {...props} onClick={this.onClick} />;
      }
    }
    
    const MyComponent = (props) => {
      const onClick = () => {
        // ...
      }
    
      return <div {...props} onClick={onClick} />;
    }
    

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 a future-proof measure that eases breaking 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

When not working with TypeScript, 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 structures 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,
  ...
});

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 keeping 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(() {
        localStorage.setItem('formData', name);
      }, [name]);
    };
    
    useEffect(() {
      if (name !== '') localStorage.setItem('formData', name);
    }, [name]);
    
  • Every value referenced inside the useEffect 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

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

Tests

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