RouteManager UI coding patterns: Generic ones
Published: 2021-07-20 06:23:00


> This is a non-exhaustive list of the coding patterns the [WorkWave RouteManager's](https://www.workwave.com/route-manager/) **front-end** team follows. The patterns are based on years of experience writing, debugging, and refactoring front-end applications with **React and TypeScript** but evolves constantly. Most of the possible improvements and the code smells are detected during the code reviews and the pair programming sessions.

*(please note: I do not work for WorkWave anymore, these patterns will not be updated)*

*(last update: 2022, March)*


- [Use named functions for components and utilities](#use-named-functions-for-components-and-utilities)
- [Use consistent names](#use-consistent-names)
- [Prefer self-explicative code instead of comments](#prefer-selfexplicative-code-instead-of-comments)
- [Code must be simple (KISS principle)](#code-must-be-simple-kiss-principle)
- [Never spread function's arguments](#never-spread-functions-arguments)
- [Use an underscore to ignore the arguments](#use-an-underscore-to-ignore-the-arguments)
- [Prefer string unions over booleans](#prefer-string-unions-over-booleans)
- [Prefer positive conditions](#prefer-positive-conditions)
- [Prefer variables over long conditions](#prefer-variables-over-long-conditions)
- [Bailouts](#bailouts)
- [Avoid fake bailouts](#avoid-fake-bailouts)
- [Prefer Nullish Coalescence over ternaries](#prefer-nullish-coalescence-over-ternaries)
- [Logical nullish assignment](#logical-nullish-assignment)
- [Prefix higher-order functions with create](#prefix-higherorder-functions-with-create)
- [Avoid default exports](#avoid-default-exports)
- [Prefer async code for promises](#prefer-async-code-for-promises)
- [Pass only the needed arguments](#pass-only-the-needed-arguments)

## Use named functions for components and utilities

Named functions ease debugging, both in the browser devTools and in the React devTools.

```ts
// ❌ don't
export const getFoo = () => {}
export const useFoo = () => {}
export const FooComponent = () => {}

// ✅ do
export function getFoo() {}
export function useFoo() {}
export function FooComponent() {}
```

Anyway, avoid them for inline functions.
```ts
// ❌ don't
useEffect(function myEffect() {})
addEventListener(function listener() {})

// ✅ do
useEffect(() => {})
addEventListener(() => {})
```

## Use consistent names

Consistent names favor moving into the codebase through the IDE's fuzzy search and reduce ambiguity.

```ts
// ❌ don't
// file: hooks/useValidateEmail.ts
function useValidateEmail() {
  /* ... rest of the code... */
}
// file: actions/emailActions.ts
function isEmailValidAction() {
  /* ... rest of the code... */
}
// file: sagas/validEmailSaga.ts
function checkEmailValidity() {
  /* ... rest of the code... */
}
// ✅ do
// file: hooks/useValidateEmail.ts
function useValidateEmail() {
  /* ... rest of the code... */
}
// file: actions/validateEmail.ts
function validateEmail() {
  /* ... rest of the code... */
}
// file: sagas/validateEmail.ts
function validateEmail() {
  /* ... rest of the code... */
}
```

## Prefer self-explicative code instead of comments

Documentation and comments are helpful, but you can reduce them with self-explanatory code.

```ts
// ❌ don't
/**
 * Filter out the non-completed orders.
 */
function util(array: Order[]) {
  const ok: Order[] = []

  for(const item of array) {
    const bool = item.status === 'completed'
    if(bool) {
      ok.push(item)
    }
  }

  return ok
}

// ✅ do
function filterOutUncompletedOrders(orders: Order[]) {
  const completedOrders: Order[] = []

  for(const order of orders) {
    if(order.status === 'completed') {
      completedOrders.push(order)
    }
  }

  return completedOrders
}
```

## Code must be simple (KISS principle)

Always prefer readability over smartness, the future-reader will thank you.

```ts
// ❌ don't
function getLabel(cosmetic?: CardCosmetic, isToday?: boolean): string {
  const pieces: string[] = []
  if (isToday) {
    pieces.push('today')
  }

  if (cosmetic === 'edge-of-selection' || cosmetic === 'selected') {
    pieces.push('selected')
  }

  return pieces.join(' ')
}

// ✅ do
function getLabel(cosmetic?: CardCosmetic, isToday?: boolean): string {
  const selected = cosmetic === 'edge-of-selection' || cosmetic === 'selected'
​
  if (isToday && selected) return 'today selected'
  if (selected) return 'selected'
  if (isToday) return 'today'

  return ''
}
```

## Never spread function's arguments

Spreading functions' arguments prevent debugging the source object, avoid it.

```ts
// ❌ don't
function foo({ bar, baz }) {
  /* ... rest of the code... */
}

// ✅ do
function foo(params) {
  const { bar, baz } = params
  /* ... rest of the code... */
}
```

## Use an underscore to ignore the arguments

Using an underscore for the ignored argument improves readability.

```ts
// ❌ don't
function foo(_event, type) {
  /* ... rest of the code... */
}
// ✅ do
function foo(_, type) {
  /* ... rest of the code... */
}
```

## Prefer string unions over booleans

String unions prevent boolean states to explode and inconsistencies.

```ts
// ❌ don't
type Status = {
  idle: boolean
  loading: boolean
  complete: boolean
  error: boolean
}

// ✅ do
type Status = 'idle' | 'loading' | 'complete' | 'error'
```


## Prefer positive conditions

Positive conditions avoid the reader's mind to reverse the variable name meaning.

```ts
// ❌ don't
const allowViewers = false
if(userType === 'viewer' && !allowViewers) {
  // ...
}

// ✅ do
const blockViewers = true
if(userType === 'viewer' && blockViewers) {
  // ...
}
```


## Prefer variables over long conditions

Variable names are way easier to read compared to long conditions.

```ts
// ❌ don't
if(userType === 'viewer' && blockViewers) {
  // ...
}
// ✅ do
const userEnabled = userType === 'viewer' && blockViewers
if(userEnabled) {
  // ...
}
```

## Bailouts

Keep pre-checks and bailouts on a single line, without braces. Reducing the function's height improves readability.
```ts
// ❌ don't
function foo() {
  if(/* condition 1 */) {
    return
  }
  if(/* condition 2 */) {
    return
  }
  if(/* condition 3 */) {
    return
  }
}

// ✅ do
function foo() {
  if(/* condition 1 */) return
  if(/* condition 2 */) return
  if(/* condition 3 */) return

  /* ... rest of the code... */
}
```

## Avoid fake bailouts

If a function returns some possible valid values, don't confuse the reader by treating one of them as a "default" value and the other ones as bailouts or early-returns.

```ts
// ❌ don't
function getMainColor(theme: 'light' | 'dark') {
  if(theme === 'dark') {
    return 'black'
  }

  return 'white'
}

// ✅ do
function getMainColor(theme: 'light' | 'dark') {
  if(theme === 'dark') {
    return 'black'
  } else {
    return 'white'
  }
}

// or

function getMainColor(theme: 'light' | 'dark') {
  switch(theme) {
    case 'light':
      return 'white'
    case 'dark':
      return 'black'
  }
}
```

## Prefer Nullish Coalescence over ternaries

Sometimes, ternaries can be compressed by using the `??` operator.

```ts
// ❌ don't
function get(key: string) {
  return object[key] ? object[key] : '-'
}
// ✅ do
function get(key: string) {
  return object[key] ?? '-'
}
```


## Logical nullish assignment

Nullish Coalescing assignment can be compressed by using the `??=` operator.

```ts
// ❌ don't
obj.foo = obj.foo ?? {}

// ✅ do
obj.foo ??= {}
```
## Prefix higher-order functions with create

Higher-order functions (functions that create other pre-configured functions) must be prefixed with "create".

```ts
// ❌ don't
function getLogger(tag: string) {
  return function logger(message: string) {
    console.log(tag, message)
  }
}

// ✅ do
function createLogger(tag: string) {
  return function logger(message: string) {
    console.log(tag, message)
  }
}
```
## Avoid default exports

Default exports force the consumer to give a name to the imported module, removing control from the module itself. Always prefer named exports.

```ts
// ❌ don't
const foo = 'bar'
export default foo

// ✅ do
export const foo = 'bar'
```

## Prefer async code for promises

Async code is more condensed and easier to read, even for non Promise-experts.

```ts
// ❌ don't
function load() {
  return service.then(() => {
    /* ... rest of the code... */
  }).cetch(() => {
    /* ... rest of the code... */
  })
}

// ✅ do
function async load() {
  try {
    await service()
    /* ... rest of the code... */
  } catch {
    /* ... rest of the code... */
  }
}
```

## Pass only the needed arguments

Functions must be passed only with the used arguments.

```ts
// ❌ don't
function isComplete(order: Order) {
  return order.status === 'complete'
}

// ✅ do
function isComplete(status: OrderStatus) {
  return status === 'complete'
}
```