DEV Community

Stefano Magni
Stefano Magni

Posted on • Edited on

RouteManager UI coding patterns: Immutability

This is a non-exhaustive list of the coding patterns the WorkWave RouteManager's 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)

We guarantee objects' immutability through ESLint. We use the no-param-reassign rule with the following configuration

'no-param-reassign': [
  'error',
  {
    props: true,
    ignorePropertyModificationsForRegex: [
      // standard array.reduce
      'acc',
      // array.reduce with params named differently from `acc`. They must end with `Acc` at least
      'Acc$',
      // standard Immer draft
      'draft',
      // Immer drafts
      'Draft$',
      // refs passed around hooks
      'Ref$',
      // parameters prefixed by `mutable`
      '^mutable',
    ],
  },
],
Enter fullscreen mode Exit fullscreen mode

Please note that enabling mutability through variable names helps the reader only if the whole chain of callers uses the prefix/suffix.

// ❌ don't
function a(array: string[]) {
  b(array)
}

function b(array: string[]) {
  c(array)
}

function c(mutableArray: string[]) {
  mutableArray.splice(1)
}

// ✅ do
function a(mutableArray: string[]) {
  b(mutableArray)
}

function b(mutableArray: string[]) {
  c(mutableArray)
}

function c(mutableArray: string[]) {
  mutableArray.splice(1)
}
Enter fullscreen mode Exit fullscreen mode

Accumulators

Array.reduce accumulators can be mutated, if named acc or suffixed by Acc.

// ❌ don't
[1, 2, 3].reduce<number>((map, item) => {
  return map[item] = item
}, 0)

// ✅ do
[1, 2, 3].reduce<number>((acc, item) => {
  return acc[item] = item
}, 0)

[1, 2, 3].reduce<number>((mapAcc, item) => {
  return mapAcc[item] = item
}, 0)
Enter fullscreen mode Exit fullscreen mode

Immer' drafts

Immer' drafts can be mutated, if named draft or suffixed by draft.

// ❌ don't
const next = produce(prev, temp => {
  temp.message = message
})

// ✅ do
const next = produce(prev, draft => {
  draft.message = message
})

const next = produce(prev, emailDraft => {
  emailDraft.message = message
})
Enter fullscreen mode Exit fullscreen mode

React's refs

React's refs can be mutated, if suffixed by Ref.

// ❌ don't
export function useNow(now) {
  useEffect(() => {
    now.current = Date.now()
  }, [])
}

export function useNow(ref) {
  useEffect(() => {
    ref.current = Date.now()
  }, [])
}

// ✅ do
export function useNow(nowRef) {
  useEffect(() => {
    nowRef.current = Date.now()
  }, [])
}
Enter fullscreen mode Exit fullscreen mode

Mutable arguments

The arguments of a function can be mutated, if prefixed by mutable.

// ❌ don't
export function setToday(dates) {
  dates.today = new Date()
}

// ✅ do
export function setToday(mutableDates) {
  mutableDates.today = new Date()
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)