A collection of JavaScript / React / React Native style guides
Here's the one for React.
NOTE: This is still a work-in-progress. Everyone can contribute just send a pull request. ;)
- Introduction
- Source Files
- References
- Modules
- Objects
- Arrays
- Destructuring
- String
- Functions
- Arrow Functions
- Iterators & Generators
- This style guide is an accumulation of coding standards from different sources (e.g. AirBnB, Google, etc.) as well as my own inputs based on my experiences with JavaScript and React.
- Optional formatting choices made in examples must not be enforced as rules.
NOTE: this style guide is following the ES6 standard
- Files
- File names must be all lowercase and may include
_
underscores or-
dashes, but no additional punctuation. Follow the convention that your project uses. File name's extension must be.js
.
- File names must be all lowercase and may include
- Special Characters
- Whitespace - Tab characters are not used for indentation.
-
Use
const
for all of your references; avoid usingvar
.Why? This ensures that you can’t reassign your references, which can lead to bugs and difficult to comprehend code.
// avoid var a = 1; var b = 2; // prefer const a = 1; const b = 2;
-
If you must reassign references, use
let
instead ofvar
.Why?
let
is block-scoped rather than function-scoped likevar
.// avoid var name = 'John Doe'; if (true) { name = 'Juan Dela Cruz'; } // prefer let name = 'John Doe'; if (true) { name = 'Juan Dela Cruz'; }
-
Always use modules (
import
/export
) over a non-standard module system. You can always transpile to your preferred module system.Why? Modules are the future, let’s start using the future now.
// avoid const StyleGuide = require('./StyleGuide'); module.exports = StyleGuide.Modules; // this is fine but... import StyleGuide from './StyleGuide'; export default StyleGuide.Modules; // prefer this import { Modules } from './StyleGuide'; export default Modules;
-
Do not use wildcard imports.
Why? This makes sure you have a single default export.
// avoid import * as StyleGuide from './StyleGuide'; // prefer import StyleGuide from './StyleGuide';
-
And do not export directly from an import.
Why? Although the one-liner is concise, having one clear way to import and one clear way to export makes things consistent.
// avoid export { Modules as default } from './StyleGuide'; // prefer import { Modules } from './StyleGuide'; export default Modules;
-
Only import from a path in one place.
Why? Having multiple lines that import from the same path can make code harder to maintain.
// avoid import React from 'react'; // … some other imports … // import { Component, PureComponent } from 'react'; // prefer import React, { Component, PureComponent } from 'react'; // prefer import React, { Component, PureComponent, } from 'react';
-
Do not export mutable bindings.
Why? Mutation should be avoided in general, but in particular when exporting mutable bindings. While this technique may be needed for some special cases, in general, only constant references should be exported.
// avoid let number = 3; export { number }; // prefer const number = 3; export { number };
-
In modules with a single export, prefer default export over named export.
Why? To encourage more files that only ever export one thing, which is better for readability and maintainability.
// bad export function doThis() {} // good function doThis() {} export default doThis; // good export default function doThis() {}
-
Put all
import
s above non-import statements.Why? Since
import
s are hoisted, keeping them all at the top prevents surprising behavior.// avoid import { Form, Field } from 'react-native-validate-form'; foo.init(); import HtmlPdfViewer from 'html-pdf-viewer'; // prefer import { Form, Field } from 'react-native-validate-form'; import HtmlPdfViewer from 'html-pdf-viewer'; foo.init();
-
Multiline imports should be indented just like multiline array and object literals.
Why? The curly braces follow the same indentation rules as every other curly brace block in the style guide, as do the trailing commas.
// avoid import {firstName, secondName, thirdName, fourthName, fifthName} from './someComponent'; // prefer import { firstName, secondName, thirdName, fourthName, fifthName, } from 'path';
-
Disallow Webpack loader syntax in module import statements.
Why? Since using Webpack syntax in the imports couples the code to a module bundler. Prefer using the loader syntax in
webpack.config.js
.// avoid import fooSass from 'css!sass!foo.scss'; import barCss from 'style!css!bar.css'; // prefer import fooSass from 'foo.scss'; import barCss from 'bar.css';
-
Use the literal syntax for object creation.
// avoid const obj = new Object(); // prefer const obj = {};
-
Use computed property names when creating objects with dynamic property names.
Why? They allow you to define all the properties of an object in one place.
function getKey(key) { return `${key}`; } // avoid const obj = { id: 1, name: 'Chocolate', }; obj[getKey('enabled')] = true; // prefer const obj = { id: 1, name: 'Chocolate', [getKey('enabled')]: true, };
-
Use object method shorthand.
// avoid const obj = { ctr: 1, addValue: function (value) { return obj.ctr + value; }, }; // prefer const obj = { ctr: 1, addValue(value) { return obj.ctr + value; }, };
-
Use property value shorthand.
Why? It is shorter to write and descriptive.
const name = 'Juan Dela Cruz'; // avoid const obj = { name: name, }; // prefer const obj = { name, };
-
Group your shorthand properties at the beginning of your object declaration.
Why? It’s easier to tell which properties are using the shorthand.
const name = 'Juan Dela Cruz'; const address = 'Metro Manila'; // avoid const obj = { age: 24, gender: 'Male', name, yearsOfExperience: 4, address }; // prefer const obj = { name, address age: 24, gender: 'Male', yearsOfExperience: 4, };
-
Only quote properties that are invalid identifiers.
Why? In general we consider it subjectively easier to read. It improves syntax highlighting, and is also more easily optimized by many JS engines.
// avoid const obj = { 'foo': 1, 'bar': 2, 'invalid-id': 3 }; // prefer const obj = { foo: 1, bar: 2, 'invalid-id': 3 };
-
Do not call
Object.prototype
methods directly, such ashasOwnProperty
,propertyIsEnumerable
, andisPrototypeOf
.Why? These methods may be shadowed by properties on the object in question - consider
{ hasOwnProperty: false }
- or, the object may be a null object (Object.create(null)
).// avoid console.log(object.hasOwnProperty(key)); // prefer console.log(Object.prototype.hasOwnProperty.call(object, key)); // or const has = Object.prototype.hasOwnProperty; // cache the lookup once, in module scope. console.log(has.call(object, key));
-
Prefer the object spread operator over
Object.assign
to shallow-copy objects. Use the object rest operator to get a new object with certain properties omitted.// never do this const orig = { a: 1, b: 2 }; const copy = Object.assign(orig, { c: 3 }); // this mutates the `orig` object delete copy.a; // so does this // avoid const orig = { a: 1, b: 2 }; const copy = Object.assign({}, orig, { c: 3 }); // copy => { a: 1, b: 2, c: 3 } // prefer const orig = { a: 1, b: 2 }; const copy = { ...orig, c: 3 }; // copy => { a: 1, b: 2, c: 3 }
-
Use the literal syntax for array creation.
// avoid const things = new Array(); // prefer const things = [];
-
Use Array#push instead of direct assignment to add items to an array.
const things = []; // avoid things[things.length] = 'Chocolate'; // prefer things.push('Chocolate');
-
Use array spreads
...
to copy arrays.// avoid const len = items.length; const itemsCopy = []; let i; for (i = 0; i < len; i += 1) { itemsCopy[i] = items[i]; } // prefer const itemsCopy = [...items];
-
To convert an iterable object to an array, use spreads
...
instead ofArray.from
.const bar = document.querySelectorAll('.foo'); // this is good but... const things = Array.from(bar); // prefer this const things = [...bar];
-
Use
Array.from
for converting an array-like object to an array.const arrayLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 }; // avoid const things = Array.prototype.slice.call(arrayLike); // prefer const things = Array.from(arrayLike);
-
Use
Array.from
instead of spread...
for mapping over iterables, because it avoids creating an intermediate array.// avoid const things = [...foo].map(bar); // prefer const things = Array.from(foo, bar);
-
Use return statements in array method callbacks. It’s ok to omit the return if the function body consists of a single statement returning an expression without side effects, following Arrow Implicit Return.
// prefer [1, 2, 3].map((x) => { const y = x + 1; return x * y; }); // prefer [1, 2, 3].map(x => x + 1); // avoid - no returned value means `acc` becomes undefined after the first iteration [[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => { const flatten = acc.concat(item); acc[index] = flatten; }); // prefer [[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => { const flatten = acc.concat(item); acc[index] = flatten; return flatten; }); // avoid inbox.filter((msg) => { const { subject, author } = msg; if (subject === 'Mockingbird') { return author === 'Harper Lee'; } else { return false; } }); // prefer inbox.filter((msg) => { const { subject, author } = msg; if (subject === 'Mockingbird') { return author === 'Harper Lee'; } return false; });
-
Use line breaks after open and before close array brackets if an array has multiple lines
// bad const arr = [ [0, 1], [2, 3], [4, 5], ]; // bad const objectInArray = [{ id: 1, }, { id: 2, }]; // bad const arr = [ 1, 2, ]; // good const arr = [[0, 1], [2, 3], [4, 5]]; const objectInArray = [ { id: 1, }, { id: 2, }, ]; const numberInArray = [ 1, 2, ];
-
Use object destructuring when accessing and using multiple properties of an object.
Why? Destructuring saves you from creating temporary references for those properties.
// avoid function getData(data) { const name = data.name; const address = data.address; return `${name} lives in ${address}`; } // prefer function getData(data) { const { name, address } = data; return `${name} lives in ${address}`; } // prefer more function getData({ name, address }) { return `${name} lives in ${address}`; }
-
Use array destructuring
const arr = [1, 2, 3, 4]; // avoid const one = arr[0]; const two = arr[1]; // prefer const [one, two] = arr;
-
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.
// avoid function processInput(input) { // ... return [one, two, three, four]; } // the caller needs to think about the order of return data const [one, __, three] = processInput(input); // prefer function processInput(input) { // ... return { one, two, three, four }; } // the caller selects only the data they need const { one, three } = processInput(input);
-
Use single quotes
''
for strings.// avoid const name = "Juan Dela Cruz"; // avoid - template literals should contain interpolation or newlines const name = `Juan Dela Cruz`; // prefer const name = 'Juan Dela Cruz';
-
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.
// avoid const longString = 'There is no one who loves or pursues or desires to obtain pain of itself, \ because it is pain, but because occasionally circumstances occur in which toil and pain \ can procure him some great pleasure.'; // avoid const longString = 'There is no one who loves or pursues or desires to obtain pain of itself, ' + 'because it is pain, but because occasionally circumstances occur in which toil and pain ' + 'can procure him some great pleasure.'; // prefer const longString = 'There is no one who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain can procure him some great pleasure.';
-
When programmatically building up strings, use template strings instead of concatenation.
Why? Template strings give you a readable, concise syntax with proper newlines and string interpolation features.
// avoid function doIt(stuff) { return 'Just do ' + stuff + '!'; } // avoid function doIt(stuff) { return ['Just do ', stuff, '!'].join(); } // avoid function doIt(stuff) { return `Just do ${ stuff }!`; } // prefer function doIt(stuff) { return `Just do ${stuff}!`; }
-
Do not unnecessarily escape characters in strings.
Why? Backslashes harm readability, thus they should only be present when necessary.
// avoid const string = '\'this\' \i\s \"quoted\"'; // prefer const string = '\'this\' is "quoted"'; const string = `This is the '${stuff}'`;
-
Use named function expressions instead of function declarations.
Why? Function declarations are hoisted, which means that it’s easy - too easy - to reference the function before it is defined in the file. This harms readability and maintainability. If you find that a function’s definition is large or complex enough that it is interfering with understanding the rest of the file, then perhaps it’s time to extract it to its own module! Don’t forget to explicitly name the expression, regardless of whether or not the name is inferred from the containing variable (which is often the case in modern browsers or when using compilers such as Babel).
// avoid function foo() { // ... } // avoid const bar = function () { // ... }; // prefer // lexical name distinguished from the variable-referenced invocation(s) const short = function longUniqueMoreDescriptiveFunctionName() { // ... };
-
Note: ECMA-262 defines a
block
as a list of statements. A function declaration is not a statement.// avoid if (truth) { function test() { console.log('Nope.'); } } // prefer let test; if (truth) { test = () => { console.log('Yup.'); }; }
-
Never name a parameter
arguments
. This will take precedence over thearguments
object that is given to every function scope.// avoid function foo(name, options, arguments) { // ... } // prefer function foo(name, options, args) { // ... }
-
Never use
arguments
, opt to use rest syntax...
instead.Why?
...
is explicit about which arguments you want pulled. Plus, rest arguments are a real Array, and not merely Array-like likearguments
.// avoid function concatenateAll() { const args = Array.prototype.slice.call(arguments); return args.join(''); } // prefer function concatenateAll(...args) { return args.join(''); }
-
Use default parameter syntax rather than mutating function arguments.
// seriously avoid function sampleFunc(opts) { opts = opts || {}; // ... } // avoid function sampleFunc(opts) { if (opts === void 0) { opts = {}; } // ... } // prefer function sampleFunc(opts = {}) { // ... }
-
Avoid side effects with default parameters.
Why? They are confusing to reason about.
var b = 1; // avoid function count(a = b++) { console.log(a); } count(); // 1 count(); // 2 count(3); // 3 count(); // 3
-
Always put default parameters last.
// avoid function sampleFunc(opts = {}, name) { // ... } // prefer function sampleFunc(name, opts = {}) { // ... }
-
Never use the Function constructor to create a new function.
Why? Creating a function in this way evaluates a string similarly to
eval()
, which opens vulnerabilities.// avoid var add = new Function('a', 'b', 'return a + b'); // avoid var subtract = Function('a', 'b', 'return a - b');
-
Spacing in a function signature.
Why? Consistency is good, and you shouldn’t have to add or remove a space when adding or removing a name.
// avoid const w = function(){}; const t = function (){}; const f = function() {}; // prefer const r = function () {}; const o = function f() {}; function l() {};
-
Never mutate parameters.
Why? Manipulating objects passed in as parameters can cause unwanted variable side effects in the original caller.
// avoid function sampleFunc(obj) { obj.key = 1; } // prefer function sampleFunc(obj) { const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1; }
-
Never reassign parameters.
Why? Reassigning parameters can lead to unexpected behavior, especially when accessing the
arguments
object. It can also cause optimization issues, especially in V8.// avoid function sampleFunc1(a) { a = 1; // ... } function sampleFunc2(a) { if (!a) { a = 1; } // ... } // prefer function sampleFunc3(a) { const b = a || 1; // ... } function sampleFunc4(a = 1) { // ... }
-
Prefer the use of the spread operator
...
to call variadic functions.Why? It’s cleaner, you don’t need to supply a context, and you can not easily compose
new
withapply
.// avoid const x = [1, 2, 3, 4, 5]; console.log.apply(console, x); // prefer const x = [1, 2, 3, 4, 5]; console.log(...x); // avoid new (Function.prototype.bind.apply(Date, [null, 2016, 8, 5])); // prefer new Date(...[2016, 8, 5]);
-
Functions with multiline signatures, or invocations, should be indented just like every other multiline list in this guide: with each item on a line by itself, with a trailing comma on the last item.
// avoid function foo(bar, baz, quux) { // ... } // prefer function foo( bar, baz, quux, ) { // ... } // avoid console.log(foo, bar, baz); // prefer console.log( foo, bar, baz, );
-
When you must use an anonymous function (as when passing an inline callback), use arrow function notation.
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.
// avoid [1, 2, 3].map(function (x) { const y = 1; return x * y; }); // prefer [1, 2, 3].map((x) => { const y = 1; return x * y; });
-
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.Why? Syntactic sugar. It reads well when multiple functions are chained together.
// avoid [1, 2, 3].map(item => { const plusOne = item + 1; `No return statement: ${plusOne}.`; }); // prefer [1, 2, 3].map(item => `This line automatically returns: ${item}.`); // prefer [1, 2, 3].map((item) => { const plusOne = item + 1; return `Multiline should have a return satement: ${plusOne}.`; }); // prefer [1, 2, 3].map((item, index) => ({ [index]: item, })); // No implicit return with side effects function foo(callback) { const val = callback(); if (val === true) { // Do something if callback returns true } } let bool = false; // avoid foo(() => bool = true); // prefer foo(() => { bool = true; });
-
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.
// avoid [1, 2, 3].map(number => myObject.prototype.hasOwnProperty.call( magic, number, ) ); // prefer [1, 2, 3].map(number => ( myObject.prototype.hasOwnProperty.call( magic, number, ) ));
-
If your function takes a single argument and doesn’t use braces, omit the parentheses. Otherwise, always include parentheses around arguments for clarity and consistency. Note: it is also acceptable to always use parentheses.
Why? Less visual clutter.
// avoid [1, 2, 3].map((number) => number + 1); // prefer [1, 2, 3].map(number => number + 1); // prefer - if the line is too long do not include it in the same line as `.map()` [1, 2, 3].map(number => ( `There is no one who loves or pursues or desires to obtain pain of itself. Here's the number though: ${number}.` )); // avoid [1, 2, 3].map(x => { const y = 1; return x * y; }); // prefer [1, 2, 3].map((x) => { const y = 1; return x * y; });
-
Avoid confusing arrow function syntax (
=>
) with comparison operators (<=
,>=
).// avoid const numberValue = number => number.value >= 101 ? 'over' : 'under'; // avoid const numberValue = (number) => number.value >= 101 ? 'over' : 'under'; // prefer const numberValue = number => (number.value >= 101 ? 'over' : 'under'); // prefer const numberValue = (number) => { const { value } = number; return value >= 101 ? 'over' : 'under'; };
-
Enforce the location of arrow function bodies with implicit returns.
// avoid these (foo) => bar; (foo) => (bar); // prefer these (foo) => bar; (foo) => (bar); (foo) => ( bar )
-
Always use
class
. Avoid manipulatingprototype
directly.Why?
class
syntax is more concise and easier to reason about.// avoid function Sample(arr = []) { this.sample = [...arr]; } Sample.prototype.pop = function () { const value = this.sample[0]; this.sample.splice(0, 1); return value; }; // prefer class Sample { constructor(arr = []) { this.sample = [...arr]; } pop() { const value = this.sample[0]; this.sample.splice(0, 1); return value; } }
-
Use
extends
for inheritance.Why? It is a built-in way to inherit prototype functionality without breaking
instanceof
.// avoid const inherits = require('inherits'); function Sample(items) { List.apply(this, items); } inherits(Sample, List); Sample.prototype.peek = function () { return this.list[0]; }; // prefer class Sample extends List { peek() { return this.list[0]; } }
-
Methods can return
this
to help with method chaining.// avoid Pirate.prototype.eat = function () { this.jumping = true; return true; }; Pirate.prototype.setCrewName = function (name) { this.name = name; }; const luffy = new Pirate(); luffy.eat(); // => true luffy.setCrewName('strawhats'); // => undefined // prefer class Pirate { eat() { this.eating = true; return this; } setCrewName(name) { this.name = name; return this; } } const luffy = new Pirate(); luffy.eat() .setCrewName('strawhats');
-
It’s okay to write a custom
toString()
method, just make sure it works successfully and causes no side effects.class Pirate { constructor(options = {}) { this.powers = options.powers || 'muggle'; } getDevilFruit() { return this.powers; } toString() { return `Pirate - ${this.getDevilFruit()}`; } }
-
Classes have a default constructor if one is not specified. An empty constructor function or one that just delegates to a parent class is unnecessary.
// avoid class Pirate { constructor() {} getDevilFruit() { return this.powers; } } // avoid class Luffy extends Pirate { constructor(...args) { super(...args); } } // prefer class Luffy extends Pirate { constructor(...args) { super(...args); this.powers = 'Gomu Gomu'; } }
-
Avoid duplicate class members.
Why? Duplicate class member declarations will silently prefer the last one - having duplicates is almost certainly a bug.
// avoid class Pirate { one() { return 1; } one() { return 2; } } // prefer class Pirate { one() { return 1; } piece() { return 2; } }
-
Don’t use iterators. Prefer JavaScript’s higher-order functions instead of loops like
for-in
orfor-of
.Why? This enforces our immutable rule. Dealing with pure functions that return values is easier to reason about than side effects.
Use
map()
/every()
/filter()
/find()
/findIndex()
/reduce()
/some()
/ ... to iterate over arrays, andObject.keys()
/Object.values()
/Object.entries()
to produce arrays so you can iterate over objects.const numbers = [1, 2, 3, 4, 5]; // avoid let sum = 0; for (let num of numbers) { sum += num; } sum === 15; // prefer let sum = 0; numbers.forEach((num) => { sum += num; }); sum === 15; // best (use the functional force) const sum = numbers.reduce((total, num) => total + num, 0); sum === 15; // avoid const increasedByOne = []; for (let i = 0; i < numbers.length; i++) { increasedByOne.push(numbers[i] + 1); } // prefer const increasedByOne = []; numbers.forEach((num) => { increasedByOne.push(num + 1); }); // best (keeping it functional) const increasedByOne = numbers.map(num => num + 1);
-
Don’t use generators for now.
Why? They don’t transpile well to ES5.
-
If you must use generators, make sure their function signature is spaced properly.
Why?
function
and*
are part of the same conceptual keyword -*
is not a modifier forfunction
,function*
is a unique construct, different fromfunction
.// avoid function * foo() { // ... } // avoid const bar = function * () { // ... }; // avoid const baz = function *() { // ... }; // avoid const quux = function*() { // ... }; // avoid function*foo() { // ... } // avoid function *foo() { // ... } // nope function * foo() { // ... } // nope const wat = function * () { // ... }; // prefer function* foo() { // ... } // prefer const foo = function* () { // ... };