Skip to content

Coding Standards

Raushan Sharma edited this page Apr 17, 2020 · 7 revisions

Introduction

This document serves as the complete definition of our coding standards for source code in the JavaScript programming language.

Like other programming style guides, this document focuses primarily on the hard-and-fast rules that we follow universally.

For the code linting, we are using ESLint which is further extended by airbnb's rules. We recommend that you get yourself familiar with the rules which are defined here - https://github.com/airbnb/javascript and https://github.com/airbnb/javascript/tree/master/react

1. Source file basics

1.1 File names must be all lowercase and may include underscores (_) or dashes (-), but no additional punctuation. Follow the convention that your project uses. Filenames’ extension must be .js.

1.2 File extensions in import paths
The .js file extension is not optional in import paths and must always be included.

Incorrect: import '../directory/file';

Correct: import '../directory/file.js';

1.3 Importing the same file multiple times
Do not import the same file multiple times. This can make it hard to determine the aggregate imports of a file. Imports have the same path, but since it doesn't align it can be hard to see.

import {short} from './long/path/to/a/file.js';
import {aLongNameThatBreaksAlignment} from './long/path/to/a/file.js';

1.4 Naming module imports
Module import names (import * as name) are lowerCamelCase names that are derived from the imported file name.

import * as libString from './lib/string.js';
import * as math from './math/math.js';
import * as vectorMath from './vector/math.js';

2. Braces

Braces are used for all control structures
Braces are required for all control structures (i.e. if, else, for, do, while, as well as any others), even if the body contains only a single statement. The first statement of a non-empty block must begin on its own line.

Disallowed:

if (someVeryLongCondition())
  doSomething();

for (let i = 0; i < foo.length; i++) bar(foo[i]);

Exception: A simple if statement that can fit entirely on a single line.

if (shortCondition()) foo();

3. Semicolons are required

Every statement must be terminated with a semicolon.

4. Local variable declarations

4.1 Use const and let
Declare all local variables with either const or let. Use const by default, unless a variable needs to be reassigned. The var keyword must not be used.

4.2 One variable per declaration
Every local variable declaration declares only one variable: declarations such as let a = 1, b = 2; are not used.

5. Destructuring

  • 5.1 Use object destructuring when accessing and using multiple properties of an object. eslint: prefer-destructuring

    Why? Destructuring saves you from creating temporary references for those properties.

    // bad
    function getFullName(user) {
      const firstName = user.firstName;
      const lastName = user.lastName;
    
      return `${firstName} ${lastName}`;
    }
    
    // good
    function getFullName(user) {
      const { firstName, lastName } = user;
      return `${firstName} ${lastName}`;
    }
    
    // best
    function getFullName({ firstName, lastName }) {
      return `${firstName} ${lastName}`;
    }

  • 5.2 Use array destructuring. eslint: prefer-destructuring

    const arr = [1, 2, 3, 4];
    
    // bad
    const first = arr[0];
    const second = arr[1];
    
    // good
    const [first, second] = arr;

  • 5.3 Use object destructuring for multiple return values, not array destructuring.

    Why? You can add new properties over time or change the order of things without breaking call sites.

    // bad
    function processInput(input) {
      // then a miracle occurs
      return [left, right, top, bottom];
    }
    
    // the caller needs to think about the order of return data
    const [left, __, top] = processInput(input);
    
    // good
    function processInput(input) {
      // then a miracle occurs
      return { left, right, top, bottom };
    }
    
    // the caller selects only the data they need
    const { left, top } = processInput(input);

6. Strings

  • 6.1 Use single quotes '' for strings. eslint: quotes

    // bad
    const name = "Capt. Janeway";
    
    // bad - template literals should contain interpolation or newlines
    const name = `Capt. Janeway`;
    
    // good
    const name = 'Capt. Janeway';

  • 6.2 Strings that cause the line to go over 100 characters should not be written across multiple lines using string concatenation.

    Why? Broken strings are painful to work with and make code less searchable.

    // bad
    const errorMessage = 'This is a super long error that was thrown because \
    of Batman. When you stop to think about how Batman had anything to do \
    with this, you would get nowhere \
    fast.';
    
    // bad
    const errorMessage = 'This is a super long error that was thrown because ' +
      'of Batman. When you stop to think about how Batman had anything to do ' +
      'with this, you would get nowhere fast.';
    
    // good
    const errorMessage = 'This is a super long error that was thrown because of Batman. When you stop to think about how Batman had anything to do with this, you would get nowhere fast.';

  • 6.3 When programmatically building up strings, use template strings instead of concatenation. eslint: prefer-template template-curly-spacing

    Why? Template strings give you a readable, concise syntax with proper newlines and string interpolation features.

    // bad
    function sayHi(name) {
      return 'How are you, ' + name + '?';
    }
    
    // bad
    function sayHi(name) {
      return ['How are you, ', name, '?'].join();
    }
    
    // bad
    function sayHi(name) {
      return `How are you, ${ name }?`;
    }
    
    // good
    function sayHi(name) {
      return `How are you, ${name}?`;
    }

  • 6.4 Never use eval() on a string, it opens too many vulnerabilities. eslint: no-eval

  • 6.5 Do not unnecessarily escape characters in strings. eslint: no-useless-escape

    Why? Backslashes harm readability, thus they should only be present when necessary.

    // bad
    const foo = '\'this\' \i\s \"quoted\"';
    
    // good
    const foo = '\'this\' is "quoted"';
    const foo = `my name is '${name}'`;

7. Arrow Functions

  • 7.1 When you must use an anonymous function (as when passing an inline callback), use arrow function notation. eslint: prefer-arrow-callback, arrow-spacing

    Why? It creates a version of the function that executes in the context of this, which is usually what you want, and is a more concise syntax.

    Why not? If you have a fairly complicated function, you might move that logic out into its own named function expression.

    // bad
    [1, 2, 3].map(function (x) {
      const y = x + 1;
      return x * y;
    });
    
    // good
    [1, 2, 3].map((x) => {
      const y = x + 1;
      return x * y;
    });

  • 7.2 If the function body consists of a single statement returning an expression without side effects, omit the braces and use the implicit return. Otherwise, keep the braces and use a return statement. eslint: arrow-parens, arrow-body-style

    Why? Syntactic sugar. It reads well when multiple functions are chained together.

    // bad
    [1, 2, 3].map((number) => {
      const nextNumber = number + 1;
      `A string containing the ${nextNumber}.`;
    });
    
    // good
    [1, 2, 3].map((number) => `A string containing the ${number + 1}.`);
    
    // good
    [1, 2, 3].map((number) => {
      const nextNumber = number + 1;
      return `A string containing the ${nextNumber}.`;
    });
    
    // good
    [1, 2, 3].map((number, index) => ({
      [index]: number,
    }));
    
    // No implicit return with side effects
    function foo(callback) {
      const val = callback();
      if (val === true) {
        // Do something if callback returns true
      }
    }
    
    let bool = false;
    
    // bad
    foo(() => bool = true);
    
    // good
    foo(() => {
      bool = true;
    });

  • 7.3 In case the expression spans over multiple lines, wrap it in parentheses for better readability.

    Why? It shows clearly where the function starts and ends.

    // bad
    ['get', 'post', 'put'].map((httpMethod) => Object.prototype.hasOwnProperty.call(
        httpMagicObjectWithAVeryLongName,
        httpMethod,
      )
    );
    
    // good
    ['get', 'post', 'put'].map((httpMethod) => (
      Object.prototype.hasOwnProperty.call(
        httpMagicObjectWithAVeryLongName,
        httpMethod,
      )
    ));

  • 7.4 Always include parentheses around arguments for clarity and consistency. eslint: arrow-parens

    Why? Minimizes diff churn when adding or removing arguments.

    // bad
    [1, 2, 3].map(x => x * x);
    
    // good
    [1, 2, 3].map((x) => x * x);
    
    // bad
    [1, 2, 3].map(number => (
      `A long string with the ${number}. It’s so long that we don’t want it to take up space on the .map line!`
    ));
    
    // good
    [1, 2, 3].map((number) => (
      `A long string with the ${number}. It’s so long that we don’t want it to take up space on the .map line!`
    ));
    
    // bad
    [1, 2, 3].map(x => {
      const y = x + 1;
      return x * y;
    });
    
    // good
    [1, 2, 3].map((x) => {
      const y = x + 1;
      return x * y;
    });

  • 7.5 Avoid confusing arrow function syntax (=>) with comparison operators (<=, >=). eslint: no-confusing-arrow

    // bad
    const itemHeight = (item) => item.height <= 256 ? item.largeSize : item.smallSize;
    
    // bad
    const itemHeight = (item) => item.height >= 256 ? item.largeSize : item.smallSize;
    
    // good
    const itemHeight = (item) => (item.height <= 256 ? item.largeSize : item.smallSize);
    
    // good
    const itemHeight = (item) => {
      const { height, largeSize, smallSize } = item;
      return height <= 256 ? largeSize : smallSize;
    };

  • 7.6 Enforce the location of arrow function bodies with implicit returns. eslint: implicit-arrow-linebreak

    // bad
    (foo) =>
      bar;
    
    (foo) =>
      (bar);
    
    // good
    (foo) => bar;
    (foo) => (bar);
    (foo) => (
       bar
    )

8. Comparison Operators & Equality

  • 8.1 Use === and !== over == and !=. eslint: eqeqeq

9. Functional component over Class component

  • [9.1] Always create functional react component(not arrow functions), if you're component needs state you can use useState and other hooks in you functional component.
    // bad
    class Listing extends React.Component {
      render() {
        return <div>{this.props.hello}</div>;
      }
    }

    // bad (relying on function name inference is discouraged)
    const Listing = ({ hello }) => (
      <div>{hello}</div>
    );

    // good
    function Listing({ hello }) {
      return <div>{hello}</div>;
    }

10. Naming

  • Filename: Use PascalCase for filenames. E.g., ReservationCard.jsx.

  • Reference Naming: Use PascalCase for React components and camelCase for their instances. eslint: react/jsx-pascal-case

    // bad
    import reservationCard from './ReservationCard';
    
    // good
    import ReservationCard from './ReservationCard';
    
    // bad
    const ReservationItem = <ReservationCard />;
    
    // good
    const reservationItem = <ReservationCard />;
  • Component Naming: Use the filename as the component name. For example, ReservationCard.jsx should have a reference name of ReservationCard. However, for root components of a directory, use index.jsx as the filename and use the directory name as the component name:

    // bad
    import Footer from './Footer/Footer';
    
    // bad
    import Footer from './Footer/index';
    
    // good
    import Footer from './Footer';

11. Props

  • Always use camelCase for prop names.

    // bad
    <Foo
      UserName="hello"
      phone_number={12345678}
    />
    
    // good
    <Foo
      userName="hello"
      phoneNumber={12345678}
    />
  • Omit the value of the prop when it is explicitly true. eslint: react/jsx-boolean-value

    // bad
    <Foo
      hidden={true}
    />
    
    // good
    <Foo
      hidden
    />
    
    // good
    <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". eslint: jsx-a11y/alt-text

    // bad
    <img src="hello.jpg" />
    
    // good
    <img src="hello.jpg" alt="Me waving hello" />
    
    // good
    <img src="hello.jpg" alt="" />
    
    // good
    <img src="hello.jpg" role="presentation" />
  • Do not use words like "image", "photo", or "picture" in <img> alt props. eslint: jsx-a11y/img-redundant-alt

    Why? Screenreaders already announce img elements as images, so there is no need to include this information in the alt text.

    // bad
    <img src="hello.jpg" alt="Picture of me waving hello" />
    
    // good
    <img src="hello.jpg" alt="Me waving hello" />
  • Use only valid, non-abstract ARIA roles. eslint: jsx-a11y/aria-role

    // bad - not an ARIA role
    <div role="datepicker" />
    
    // bad - abstract ARIA role
    <div role="range" />
    
    // good
    <div role="button" />
  • Do not use accessKey on elements. eslint: jsx-a11y/no-access-key

Why? Inconsistencies between keyboard shortcuts and keyboard commands used by people using screenreaders and keyboards complicate accessibility.

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

// good
<div />

Why? Not using a stable ID is an anti-pattern because it can negatively impact performance and cause issues with component state.

We don’t recommend using indexes for keys if the order of items may change.

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

// good
{todos.map(todo => (
  <Todo
    {...todo}
    key={todo.id}
  />
))}
  • Always define explicit defaultProps for all non-required props.

Why? propTypes are a form of documentation, and providing defaultProps means the reader of your code doesn’t have to assume as much. In addition, it can mean that your code can omit certain type checks.

// bad
function SFC({ foo, bar, children }) {
  return <div>{foo}{bar}{children}</div>;
}
SFC.propTypes = {
  foo: PropTypes.number.isRequired,
  bar: PropTypes.string,
  children: PropTypes.node,
};

// good
function SFC({ foo, bar, children }) {
  return <div>{foo}{bar}{children}</div>;
}
SFC.propTypes = {
  foo: PropTypes.number.isRequired,
  bar: PropTypes.string,
  children: PropTypes.node,
};
SFC.defaultProps = {
  bar: '',
  children: null,
};