Skip to content

Instantly share code, notes, and snippets.

@MarcusHurney
Last active December 19, 2018 09:38
Show Gist options
  • Select an option

  • Save MarcusHurney/b17ebf5d863ddc7ebc71e042c7a2bfa8 to your computer and use it in GitHub Desktop.

Select an option

Save MarcusHurney/b17ebf5d863ddc7ebc71e042c7a2bfa8 to your computer and use it in GitHub Desktop.

IOHK JS / CSS Best Practices

An evolving set of best practices for writing maintainable Javascript and CSS

Table of Contents

  1. Javascript
  2. React
  3. CSS
  4. Other

Javascript

Variables

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;

Naming Conventions

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);
  }
};

Whitespace

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);
}

Modules

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';

Properties

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');

Comparison Operators & Equality

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;
}

Blocks

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;
    }
  }
}

Control Statements

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;
}

Strings

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`;

Arrow Functions

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,
}));

React

Naming

  • Extensions: Use .js extension for React components.

  • Filenames: Use PascalCase for component filenames. πŸ‘‰ TransactionsList.js.

  • Component Naming: Use the filename as the component name. For example, TransactionsList.js should contain a component named TransactionsList.

  • HOC Naming: Use camelCase to name a higher-order component. Also, use the component name as the filename. For example, withTheme.js should contain an HOC named withTheme.

  • HOC displayName: To create the displayName of a higher-order component, combine the HOC's name with the passed-in component’s name. For example, if the HOC's name is withTheme and the passed-in component's name is TransactionsList, the result is πŸ‘‰ withTheme(TransactionsList).

  • Props Naming: Avoid using DOM component prop names for different purposes. People expect props like style and className to mean one specific thing.

Alignment

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 />}

Spacing

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 } />

Quotes

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" }} />

Props

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} />;
}

Parentheses

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>);

CSS

Formatting Selectors

  • 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 {
  // ...
}

Properties

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
}

Sass

  • Always use the .scss extension 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!
    }
  }
}

Variables

Use dash-cased CSS variable names (e.g. $--font-regular) over camelCased or snake_cased variable names. Begin a variable name with two dashes --.

Comments

  • 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

Other

Git

Branch Types

  • The Daedalus master branch is protected by develop. Only develop is merged into master.
  • 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 into master.
  • To make additions to the Daedalus codebase, create a branch based from develop.
  • There are three main branch types --> feature, chore, and fix.
  • A feature branch adds a new feature to Daedalus that does not yet exist.
  • A chore branch is appropriate when cleaning up or refactoring a section within the codebase.
  • A fix branch is used to introduce a fix for a bug.

Branch Prefix

  • 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-guide
  • chore/ddw-225-refactor-api-to-use-v1
  • fix/ddw-315-fix-wallet-stuck-syncing

Commits

Commit messages should begin with the YouTrack issue ID.

Example: git commit -m "[DDW-41] Adds Git section to Best Practices"

CHANGELOG

  • 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.

⬆ back to top

βœ… Do

🚫 Don't

βœ… Do

🚫 Don't

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment