My coding convention for React projects

In this post, I will share my coding convention for my React projects.

If you think they are useful for your project especially at the initial setup stage of the project, please feel free to pick up any of them and apply to your team. Although these rules are rather opinionated based on my personal experience, comments are always welcome.

C1 Quotation.

For string props of HTML element, must use double quote ("). In other places, must use single quote (').

// bad
import * as util from "util"
// good
import * as util from 'util'

// bad
<Foo bar='bar'/>
// good
<Foo bar="bar"/>

// bad
<Foo bar={baz + "baaz"}/>
// good
<Foo bar={`${baz}baaz`}/>

C2 No operator at EOL.

An operator may not be placed at the of line.

// bad
const f = (foo, bar) =>
	foo + bar
// good
const f = (foo, bar) => foo + bar

// bad
const f = (foo, bar) => foo +
	bar
// good
const f = (foo, bar) => foo
	+ bar

// bad
const foo = isBar ?
	baz :
	baaz
// good
const foo = isBar
	? baz
	: baaz

C3 Use dashed css class names.

No camel case, use the dash-separated name in css.

// bad
.fooBar {
	width: 100%;
}
// good
.foo-bar {
	width: 100%;
}

C4 Class names joining.

All following patterns are acceptable.

<span className="bold no-wrap">lorem ipsum</span>
<span className={'bold no-wrap'}>lorem ipsum</span>
<span className={['bold', 'no-wrap'].join(' ')}>lorem ipsum</span>
<span className={[styles.fooBar].join(' ')}>lorem ipsum</span>
<span className={styles.fooBar}lorem ipsum</span>

However, see the next rule C5.

C5 Conditional class names.

Use .filter(Boolean).join(' ') to join conditional class names.

<span className={[bold && 'bold', styles.fooBar, active && styles.active].filter(Boolean).join(' ')}>lorem ipsum</span>

Use joinClassNames() function when there is a conditional class name.

export const joinClassNames = (...classNames: ReadonlyArray<string | boolean | undefined>) => classNames
	.filter(Boolean)
	.join(' ')

<span className={styles.fooBar}>lorem ipsum</span>
<span className="bold no-wrap">lorem ipsum</span>
<span className={joinClassNames(bold && 'bold', styles.fooBar, active && styles.active)}>lorem ipsum</span>

Or

import jcls from 'jcls'

<span className={styles.fooBar}>lorem ipsum</span>
<span className="bold no-wrap">lorem ipsum</span>
<span className={jcls(bold && 'bold', styles.fooBar, active && styles.active)}>lorem ipsum</span>

Note: clsx is a popular library that has a similar purpose with joinClassNames.

I also published a very simple jcls package for this purpose. It also support class removal with the rcls function.

C6 No class component.

Use functional component with hooks, no class component except when have to, such as Error Boundary component.

C7 Preserve component display name.

When declaring a component, use module top-level function declaration to preserve the component's display name in the production build.

Note: this requires babel-plugin-add-react-displayname is enabled in production builds.

// bad
export default () => <div>hello world!</div>
const MyComp = () => <div>hello world!</div>
// good
export default function MyComp() {
	return <div>hello world!</div>
}

// bad
export default forwardRef(function MyComp(props, ref) {
	return <div ref={ref}>hello world!</div>
})
// good
function MyComp(props, ref) {
	return <div ref={ref}>hello world!</div>
 }
 export default forwardRef(MyComp)

Note: because of a bug, the plugin does not set the display name as expected when the component in a forwardRef call returns a Fragment. You can wait for a fixed release of the plugin or manually set the display name.

function MyComponent(props, ref) {
	return <><div ref={ref}>hello world!</div></>
}
// fixme when babel-plugin-add-react-displayname releases bug fix, remove this line
MyComponent.displayName = 'MyComponent'
export default forwardRef(MyComponent)

C8 Use tab, no space

Use tab for indentation, no space.

// bad
foo(
  bar
)
// good
foo(
	bar
)

C9 No semicolon

No use of semicolon unless when have to, such as in for loops. In some cases, explicit separation is required, such as before inline function calls, typecasting, ..., use void (more preferred) or semicolon (only when void cannot be used) right before the inline statements which require the explicit separation.

Note: typescript declaration does not require a semicolon.

// bad
const baaz = baz + 1;
// good
const baaz = baz + 1

// bad
const baaz = baz;
['ab', 'b'].forEach(console.log)
// good
const baaz = baz
void ['ab', 'b'].forEach(console.log)

// bad
const baaz = baz;
(async () => {
	// some async calls
})()
// good
const baaz = baz
void (async () => {
	// some async calls
})()

// good
for (let i = 0; i < str.length; i++) {
	// process character
}

// bad
let foo: {bar: number; baaz: string}
// good
let foo: {
	bar: number
	baaz: string
}

// bad
import {promisify} from 'util';
// good
import {promisify} from 'util'

// bad
import {promisify} from 'util'

;(async () => {await mongoose.connect()})()
// good
import {promisify} from 'util'

void (async () => {await mongoose.connect()})()

// bad
const a = 1;
(myFunc as any)()
// good
const a = 1
;(myFunc as any)()

C10 Multiple lines props.

When component props are declared in multiple lines, each prop must start a new line.

When component props are declared in multiple lines, tag close must be placed in a separated line.

// bad
<Comp foo="bar" className={joinClassNames(
	styles.foo,
	active && 'active'
)}
/>
// good
<Comp foo="bar" className={joinClassNames(
	styles.foo,
	active && 'active'
)}/>

// bad
<Comp foo="bar"
	bar="baaz"/>
<Comp foo="bar"
	bar="baaz"
	/>
// good
<Comp foo="bar" bar="baaz"/>
<Comp
	foo="bar"
	bar="baaz"
	/>

// bad
<Comp foo="bar" baz={calc(
	baaz,
	true
)} baaz="baar"
>hello!</Comp>
// good
<Comp foo="bar" baz={calc(
	baaz,
	true
)} baaz="baar">hello!</Comp>
<Comp
	foo="bar"
	baz={calc(
		baaz,
		true
	)}
	baaz="baar"
>hello!</Comp>

C11 Trailing comma

Trailing commas are acceptable, and optional.

Any of followings are acceptable.

func(a, b, c)
func(a, b, c,)
func(
	a,
	b,
	c,
)
func(
	a,
	b,
	c
)
const a = {
	b: 'c',
	d: 'e'
}
const a = {
	b: 'c',
	d: 'e',
}
const a = [1, 2]
const a = [1, 2,]
const a = [
	1,
	2
]
const a = [
	1,
	2,
]

C12 Nested ternary operator.

Nested ternary operators are recommended with appropriate indentations.

return isFoo
	? bar
	: isBaz
		? baaz
		: baaz

C13 Parenthesis.

Eliminate parenthesis when no need to use. Refer to Operator Precedence to acknowledge the execution strategy.

// bad
(foo && (bar || baz)) || (foo && bar)
// good
foo && (bar || baz) || foo && bar

// bad
return (
	<div>
		lorem ipsum
	</div>
)
// good
return <div>
	lorem ipsum
</div>

C14 Curly braces.

Curly braces can be omitted when not required.

Any of the following are acceptable.

// not recommeneded
if (isVerbose)
	console.log('hello, world')
// recommeneded
if (isVerbose) console.log('hello, world')
if (isVerbose) {
	console.log('hello, world')
}
if (isVerbose) console
	.log('hello, world')
if (isVerbose) console.log(
	'hello, world'
)
if (
	isVerbose
) console.log(
	'hello, world'
)

// recommended
const a = () => b
const a = () => {
	return b
}

// not recommeneded
for (let i = 0; i < 10; i++)
	console.log(i)
// recommended
for (let i = 0; i < 10; i++) console.log(i)
for (
	let i = 0;
	i < 10;
	i++
) console.log(i)

C15 Literal template.

Must use the literal template for string concatenation. Recommend using the literal template when there is a new line.

// bad
const bar = 'hello, ' + bar
// good
const bar = `hello, ${bar}`

// not recommended
const bar = 'hello\nworld'
// recommended
const bar =`hello
wolrd`

C16 Style import.

When import a css style, use default import.

// bad
import * as styles from './Comp.scss'
import {active, lastChild} from './Comp.scss'
// good
import styles from './Comp.scss' 

C17 Strict comparison.

Use strict comparison, no loose comparison.

// bad
a == b
// good
a === b

C18 Closing braces' indentation.

If open brace ((, {, [) is at an end of a line, then

  • the corresponding closing brace (), }, ]) must starts a new line
  • and, both must have the same indentation level.
// bad
if (x
) {}
if (
	x) {}
// good
if (
	x
) {}
if (x) {}

// bad
if (x) {
	console.log('hi')}
// good
if (x) {console.log('hi')}
if (x) {
	console.log('hi')
}

// bad
const a = [1,
	2,
]
const b = [
	1,
	2]
const b = [
	1,
	2
	]
// good
const a = [1,
	2,]
const b = [
	1,
	2,
]

C19 Basically, upper relative imports should be avoided.

While this rule is not strict, developers should use absolute imports to import modules in parent directories. This will help the later project refractory much easier.

// not recommended
import Popup from '../components/shared/Popup'

// recommended
import Popup from '~shared/Popup'
// or
import Popup from '~/client/components/shared/Popup'

C20 Use method shorthand definition

Always prefer method shorthand definition whenever possible.

// bad
const foo = {
	bar: () => 10
}
// good
const foo = {
	bar() { return 10 }
}

// bad
interface IFoo {
	bar?: async (baz: number) => void
}
// good
interface IFoo {
	async bar?(baz: number): void
}

Exception: when the arrow function must be used to achieve lexical scope, arrow function syntax is allowed.

C21: Comma operator

Comma operator is allowed, but must be explicitly wrapped by double parenthesis.

Exception: double parenthesis is not required in for loop statements.

// bad
const map = entries.reduce((acc, [key, val]) => (acc[key] = val, acc), {})
// good
const map = entries.reduce((acc, [key, val]) => ((acc[key] = val, acc)), {})

// good
for (i = 0, j = n - 1; i < n; i++, j--) console.log(i, j)

// bad
const f = (a, b) => void (console.log(a), a  + b)
// good
const f = (a, b) => void ((console.log(a), a  + b))