An evolving set of best practices for writing maintainable Javascript and CSS
Use const instead of var for all of your variable declarations that will not be reassigned .
β Do
const a = 1;
const b = 2;π« Don't
var a = 1;
var b = 2;To reassign references, use let instead of var.
β Do
let count = 1;
if (true) {
count += 1;
}π« Don't
var count = 1;
if (true) {
count += 1;
}Declare variables separately as individual statements where each has its own line. Chaining variable declarations together with trailing commas is brittle and error prone.
β Do
const firstVar;
const secondVar;
const thirdVar;π« Don't
const header,
topnav,
content;Avoid single letter names for variables, functions, and object properties.
π« Don't
const a = 1;
const b = () => {};
const c = {
d: false,
e() {}
};A variable's name should be descriptive so it indicates how/where it is used and/or clearly communicates the significance of the data it references.
β Do
const age = 20;
const isAdult = age => {
return age >= 18;
};π« Don't
const val = 20;
const calc = data => {
return data >= 18;
};The name of a function or method should clearly communicate intended functionality. The name should indicate what the function does, not how it is done.
β Do
const robot = {
greeting: 'Hello master!',
greet() {
alert(this.greeting);
}
};π« Don't
const robot = {
phrase: 'Hello master!',
talk() {
alert(this.phrase);
}
};Place 1 space before the leading brace.
β Do
function test() {
console.log('test');
}
dog.set('attr', {
age: '1 year',
breed: 'Bernese Mountain Dog',
});π« Don't
function test(){
console.log('test');
}
dog.set('attr',{
age: '1 year',
breed: 'Bernese Mountain Dog',
});Place 1 space before the opening parenthesis in control statements (if, while, for, etc...). Place no space between the argument list and the function name in function calls and declarations.
β Do
if (isJedi) {
fight();
}
function fight() {
console.log('Swooosh!');
}π« Don't
if(isJedi) {
fight ();
}
function fight () {
console.log ('Swooosh!');
}Set off operators with spaces.
β Do
const x = y + 5;π« Don't
const x=y+5;Add a space after each curly brace when using the destructuring assignment syntax with objects.
β Do
const { inputs, outputs } = txn;π« Don't
const {inputs, outputs} = txn;Do not pad your blocks with blank lines.
π« Don't
function bar() {
console.log(foo);
}
if (baz) {
console.log(qux);
} else {
console.log(foo);
}
class Foo {
constructor(bar) {
this.bar = bar;
}
}β Do
function bar() {
console.log(foo);
}
if (baz) {
console.log(qux);
} else {
console.log(foo);
}Use named exports instead of default exports when possible.
β Do
export const foo = () => {};const bar = () => {};
export bar;Only import from a path in one place.
β Do
import foo, { named1, named2 } from 'foo';import foo, {
named1,
named2,
} from 'foo';π« Don't
import foo from 'foo';
// β¦ some other imports β¦ //
import { named1, named2 } from 'foo';Put all import statements above non-import statements.
β Do
import { foo } from 'foo';
import { bar } from 'bar';
foo.init();π« Don't
import { foo } from 'foo';
foo.init();
import { bar } from 'bar';Multiline imports should be indented just like multiline array and object literals.
β Do
import {
longNameA,
longNameB,
longNameC,
longNameD,
longNameE,
} from 'path';π« Don't
import { longNameA, longNameB, longNameC, longNameD, longNameE } from 'path';Use dot notation when accessing properties.
β Do
const luke = {
jedi: true,
age: 28,
};
const isJedi = luke.jedi;π« Don't
const luke = {
jedi: true,
age: 28,
};
const isJedi = luke['jedi'];Use bracket notation [] when accessing properties with a variable.
β Do
const luke = {
jedi: true,
age: 28,
};
function getProp(prop) {
return luke[prop];
}
const isJedi = getProp('jedi');Use === and !== over == and != to avoid unintented coercions by the ToBoolean abstract method that evaluates expressions in JavaScript.
- Objects evaluate to true
- Undefined evaluates to false
- Null evaluates to false
- Booleans evaluate to the value of the boolean
- Numbers evaluate to false if +0, -0, or NaN, otherwise true
- Strings evaluate to false if an empty string
'', otherwise true
// objects, empty or not, evaluate to true
if ({}) {
// true
}
// an array (even an empty one) is an object
if ([0] && []) {
// true
}Use shortcuts for booleans, but explicit comparisons for strings and numbers.
β Do
if (isValid) {
// ...
}
if (name !== '') {
// ...
}
if (collection.length > 0) {
// ...
}π« Don't
if (isValid === true) {
// use a comparison shortcut for booleans
}
if (name) {
// can unexpectedly evaluate to truthy
// use explicit comparison instead
}
if (collection.length) {
// ...
}
// Example of unexpected truthy evaluation for a string.
// Result: alerts "It's true".
const name = ' ';
if (name) {
alert("It's true");
} else {
alert("It's false")
}When writing a switch statement, use curly braces to create block scope for each case and default clause that contain lexical declarations. When each case/default clause has its own block scope, it prevents unintentional pollution of the namespace with duplicate lexical declarations.
β Do
switch (foo) {
case 1: {
let x = 1;
break;
}
case 2: {
let x = 2;
break;
}
case 3: {
const y = 1;
function f() {
alert(y);
}
break;
}
case 4:
const y = 2;
function f() {
alert(y);
}
break;
default: {
class C {}
}
}π« Don't
switch (foo) {
case 1:
let x = 1;
break;
case 2:
let x = 2;
break;
case 3:
const y = 1;
break;
default:
const y = 2;
class C {}
}Ternaries should not be nested and generally be single line expressions.
π« Don't
const foo = maybe1 > maybe2
? "bar"
: value1 > value2 ? "baz" : null;β Do
// split into 2 separated ternary expressions
const maybeNull = value1 > value2 ? 'baz' : null;
// better, but still uses multiple lines
const foo = maybe1 > maybe2
? 'bar'
: maybeNull;
// single line is best
const foo = maybe1 > maybe2 ? 'bar' : maybeNull;Avoid unneeded ternary statements.
π« Don't
const foo = a ? a : b;
const bar = c ? true : false;
const baz = c ? false : true;β Do
const foo = a || b;
const bar = !!c;
const baz = !c;When mixing operators, enclose them in parentheses. The only exception is the standard arithmetic operators (+, -, *, & /) since their precedence is broadly understood.
β Do
const foo = (a && b < 0) || c > 0 || (d + 1 === 0);
const bar = (a ** b) - (5 % d);
if (a || (b && c)) {
return d;
}
const bar = a + b / c * d;π« Don't
const foo = a && b < 0 || c > 0 || d + 1 === 0;
const bar = a ** b - 5 % d;
// one may be confused into thinking (a || b) && c
if (a || b && c) {
return d;
}Use braces with all multi-line blocks.
β Do
if (test) return false;
if (test) { return false; }
if (test) {
return false;
}
function bar() {
return false;
}π« Don't
if (test)
return false;If youβre using multi-line blocks with if and else, put else on the same line as your if blockβs closing brace.
β Do
if (test) {
thing1();
thing2();
} else {
thing3();
}π« Don't
if (test) {
thing1();
thing2();
}
else {
thing3();
}If an if block always executes a return statement, the subsequent else block is unnecessary. A return in an else if block following an if block that contains a return can be separated into multiple if blocks.
β Do
function foo() {
if (x) {
return x;
}
return y;
}
function cats() {
if (x) {
return x;
}
if (y) {
return y;
}
}
function dogs(x) {
if (x) {
if (z) {
return y;
}
}
return z;
}π« Don't
function foo() {
if (x) {
return x;
} else {
return y;
}
}
function cats() {
if (x) {
return x;
} else if (y) {
return y;
}
}
function dogs() {
if (x) {
return x;
} else {
if (y) {
return y;
}
}
}If your control statement (if, while etc.) exceeds the maximum line length, each (grouped) condition could be put into a new line. The logical operator should begin the line.
- Requiring operators at the beginning of the line persists alignment and follows a pattern similar to method chaining. This visual improvement assists the reader in following complex logic when reading the statement.
β Do
if (
foo === 123
&& bar === 'abc'
) {
thing1();
}
if (
(foo === 123 || bar === 'abc')
&& doesItLookGoodWhenItBecomesThatLong()
&& isThisReallyHappening()
) {
thing1();
}
if (foo === 123 && bar === 'abc') {
thing1();
}π« Don't
if ((foo === 123 || bar === 'abc') && doesItLookGoodWhenItBecomesThatLong() && isThisReallyHappening()) {
thing1();
}
if (foo === 123 &&
bar === 'abc') {
thing1();
}
if (foo === 123
&& bar === 'abc') {
thing1();
}
if (
foo === 123 &&
bar === 'abc'
) {
thing1();
}When a function contains a control statement, always return early if the return value is known or null.
β Do
function meetsMinimum(amount) {
// return early
if (!amount) return;
return amount >= 1;
}π« Don't
function txnIsPending(txn) {
if (txn && txn.status === 'creating') {
return true;
}
return false;
}Use single quotes ''.
β Do
const name = 'Ada Lovelace';π« Don't
const name = "Mary Poppins";
const name = `Mary Poppins`;Template string literals should contain interpolation of data or newlines.
β Do
const firstName = 'Ada';
const lastName = 'Lovelace';
const fullName = `${firstName} ${lastName}`;
const bio = `
My name is ${fullName}. I was born in London, England.
The year was 1815 and winter was upon us.
`;π« Don't
const fullName = `Ada Lovelace`;If your function takes a single argument, omit the parentheses for less visual clutter.
β Do
[1, 2, 3].map(x => x * x);
[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!`
));π« Don't
[1, 2, 3].map((x) => x * x);
[1, 2, 3].map((x) => {
const y = x + 1;
return x * y;
});Enforce the location of arrow function bodies with implicit returns.
β Do
foo => bar;
foo => (bar);
foo => (
bar
)π« Don't
foo =>
bar;
foo =>
(bar);In case the expression spans over multiple lines, wrap it in parentheses for better readability.
β Do
['get', 'post', 'put'].map(httpMethod => (
Object.prototype.hasOwnProperty.call(
httpMagicObjectWithAVeryLongName,
httpMethod,
)
));π« Don't
['get', 'post', 'put'].map(httpMethod => Object.prototype.hasOwnProperty.call(
httpMagicObjectWithAVeryLongName,
httpMethod,
)
);If the function body consists of a single statement omit the braces and use the implicit return.
β Do
[1, 2, 3].map(number => `A string containing the ${number}.`);
[1, 2, 3].map((number, index) => ({
[index]: number,
}));-
Extensions: Use
.jsextension for React components. -
Filenames: Use PascalCase for component filenames. π
TransactionsList.js. -
Component Naming: Use the filename as the component name. For example,
TransactionsList.jsshould contain a component namedTransactionsList. -
HOC Naming: Use camelCase to name a higher-order component. Also, use the component name as the filename. For example,
withTheme.jsshould contain an HOC namedwithTheme. -
HOC displayName: To create the
displayNameof a higher-order component, combine the HOC's name with the passed-in componentβs name. For example, if the HOC's name iswithThemeand the passed-in component's name isTransactionsList, the result is πwithTheme(TransactionsList). -
Props Naming: Avoid using DOM component prop names for different purposes. People expect props like
styleandclassNameto mean one specific thing.
Follow these alignment styles for JSX.
π« Don't
<Foo superLongParam="bar"
anotherSuperLongParam="baz" />β Do
<Foo
superLongParam="bar"
anotherSuperLongParam="baz"
/>
// if all props fit on one line, that's good!
<Foo bar="bar" />π« Don't
{showButton &&
<Button />
}
{
showButton &&
<Button />
}β Do
{showButton && (
<Button />
)}
// if the expression fits on one line, that's good!
{showButton && <Button />}Always include a single space in your self-closing tag.
β Do
<Foo />π« Don't
<Foo/>
<Foo
/>
<Foo />Do not pad JSX curly braces with spaces.
β Do
<Foo bar={baz} />π« Don't
<Foo bar={ baz } />Always use double quotes "" for JSX attributes.
Use single quotes '' for all other JS.
β Do
<Foo bar="bar" />
<Foo style={{ left: '20px' }} />π« Don't
<Foo bar='bar' />
<Foo style={{ left: "20px" }} />Always use camelCase for prop names.
β Do
<Foo
userName="hello"
phoneNumber={12345678}
/>π« Don't
<Foo
UserName="hello"
phone_number={12345678}
/>Omit the value of the prop when it is explicitly true.
β Do
<Foo hidden />π« Don't
<Foo hidden={true} />Filter out unused props when using the spread operator to pass props.
β Do
render() {
const { unusedProp1, unusedProp2, ...usedProps } = this.props;
return <WrappedComponent {...usedProps} />;
}π« Don't
render() {
const { unusedProp1, unusedProp2, ...usedProps } = this.props;
return <WrappedComponent {...this.props} />;
}Wrap JSX tags in parentheses when they span more than one line. Multiline JSX should not start on the same line as a parentheses.
β Do
// single line
render() {
const body = <div>hello</div>;
return <MyComponent>{body}</MyComponent>;
}
// multiline
render() {
return (
<MyComponent foo="bar">
<MyChild />
</MyComponent>
);
}
// both
render() {
const body = (
<div>
<h1>Hello</h1>
</div>
);
return <MyComponent>{body}</MyComponent>;
}
// stateless single line
const Name = () => <h1>Bobby Smith</h1>;
// stateless multiline
const Names = () => (
<div>
<h1>Bobby Smith</h1>
<h1>Brenda Smith</h1>
</div>
);π« Don't
render() {
return <MyComponent variant="long body" foo="bar">
<MyChild />
</MyComponent>;
}
render() {
return (<MyComponent variant="long body" foo="bar">
<MyChild />
</MyComponent>);
}
const Names = () => (<div>
<h1>Bobby Smith</h1>
<h1>Brenda Smith</h1>
</div>);- Prefer class selectors instead of ID selectors.
- Prefer camelCase or dashed-case instead of PascelCase or underscores (
_). - When using multiple selectors in a rule declaration, give each selector its own line.
- Put a space before the opening brace
{in rule declarations. - In properties, put a space after, but not before, the
:character. - Put closing braces
}of rule declarations on a new line. - Put blank lines between rule declarations.
β Do
.avatar {
border-radius: 50%;
border: 2px solid white;
}
.one,
.selector,
.perLine {
// ...
}π« Don't
.avatar{
border-radius:50%;
border:2px solid white; }
.no, .nope, .not_good {
// ...
}
#id_selector {
// ...
}The properties of a selector should always be defined in ABC order.
β Do
.supportRequest {
bottom: 0;
position: absolute;
width: auto;
}π« Don't
.title {
text-align: center;
cursor: default;
margin-bottom: 8.5px;
line-height: 1.2;
font-family: var(--font-regular);
font-size: 20px;
}End every property declaration with a semicolon for consistency and extensibility.
β Do
.test {
display: block;
height: 100px;
}π« Don't
.test {
display: block;
height: 100px
}- Always use the
.scssextension when creating a Sass file. - Nested selectors go last and nothing goes after them.
- Add whitespace between your rule declarations and nested selectors, as well as between adjacent nested selectors.
- The properties of nested selectors should always be defined in ABC order.
β Do
.btn {
background: green;
font-weight: bold;
@include transition(background 0.5s ease);
.icon {
margin-right: 10px;
}
}π« Don't
.btn {
width: 30px;
background: green;
font-weight: bold;
.icon {
margin-right: 10px;
}
@include transition(background 0.5s ease);
}Define nested selectors in the same order they are applied to their associated HTML.
Example:
import React from 'react';
const Modal = () => (
<div className="modal">
<h3 className="title">Your Modal</h3>
<button className="btn">Accept</button>
<span className="close">X</span>
</div>
);β Do
.modal {
height: 300px;
width: 400px;
z-index: 1;
.title {
// ...
}
.btn {
// ...
}
.close {
// ...
}
}π« Don't
.modal {
height: 300px;
width: 400px;
z-index: 1;
.btn {
// ...
}
.close {
// ...
}
.title {
// ...
}
}Do not nest selectors more than three levels deep. When selectors become this long, you're likely writing CSS that is:
- Strongly coupled to the HTML (fragile) -- or --
- Overly specific -- or --
- Not reusable
π« Don't
.pageContainer {
.content {
.profile {
// STOP!
}
}
}Use dash-cased CSS variable names (e.g. $--font-regular) over camelCased or snake_cased variable names. Begin a variable name with two dashes --.
- Prefer line comments (
//for Sass) to block comments. - Prefer comments on their own line. Avoid end-of-line comments.
- Write detailed comments for code that isn't self-documenting:
- Uses of z-index
- Compatibility or browser-specific hacks
- The Daedalus
masterbranch is protected bydevelop. Onlydevelopis merged intomaster. - A release branch contains the version in its name (
release/x.y.z). - Release branches are based from
master, but their version specific changes are not merged intomaster. - To make additions to the Daedalus codebase, create a branch based from
develop. - There are three main branch types -->
feature,chore, andfix. - A
featurebranch adds a new feature to Daedalus that does not yet exist. - A
chorebranch is appropriate when cleaning up or refactoring a section within the codebase. - A
fixbranch is used to introduce a fix for a bug.
- A branch prefix is a combination of its type (
feature,chore,fix) and its YouTrack issue ID. - YouTrack issue ID's for Daedalus start with the letters
DDW-+ issue number.
Examples:
feature/ddw-41-create-daedalus-best-practices-guidechore/ddw-225-refactor-api-to-use-v1fix/ddw-315-fix-wallet-stuck-syncing
Commit messages should begin with the YouTrack issue ID.
Example: git commit -m "[DDW-41] Adds Git section to Best Practices"
- The CHANGELOG is organized by release number.
- Each release contains 3 sections that match the branch types:
features,chores,fixes. - Add your entry to the appropriate section based on its content.
- Add 1 entry per PR and include a URL to the PR at the end of your entry.
- Entries are organized in ABC order.
- Write your entry in past tense.
β Do
π« Don't
β Do
π« Don't