Optionally chaining String.replace()

Using ternaries and a bit of regex magic, we can add inline logic to String.replace() statements. Here's a slightly contrived example, using the named function parameters pattern:

const presentableUrl = (fullUrl, { removeProtocol = true, removeWww = false } = {}) =>
    .replace(/(https?:\/\/)/i, removeProtocol ? '' : '$1')
    .replace(/(www\.)/i, removeWww ? '' : '$1')


What's going on here, you ask?

String.replace() has two important properties: It returns a new string, so we can chain calls; and it allows us to replace a substring with any matched group in the search pattern. This is done with a special string like '$1', where the number after the $ represents the index of the group, counting from 1. This means that by grouping our entire pattern (i.e. surrounding it with round brackets), we can then refer to the entire match as '$1'.

We then use the original match as the falsy expression of a ternary statement. This means that if the condition evaluates to false, it returns an identical string, effectively short-circuiting that specific replace.

So the line

fullUrl.replace(/(https?:\/\/)/i, removeProtocol ? '' : '$1')

can be translated to pseudocode as

replace _match_ with
    if removeProtocal is true
        _match_ /* replace it with itself */


Writing functions that perform a sequence of string transformations is quite common. In most cases we want the functions to be pure — a string goes in, a string comes out, and that's that. Simple. One-linery. Arrow-functiony.

However, if we want relatively generic functions, we sometimes need to allow optional transformations. So where do we insert that condition in our function's logic? We could just put it in an if statement. The earlier example would become:

function presentableUrl (fullUrl, { removeProtocol = true, removeWww = false } = {}) {
  let url = fullUrl

  if (removeProtocol) {
    url = url.replace(/https?:\/\//i, '')

  if (removeWww) {
      url = url.replace(/www\./i, '')

  return url

A bit of cringe-worthy, isn't it? We now have internal variable to track, and any condition we add in the future brings along another if statement and 3 lines of code. It's gonna get long. Also, it's not immeadtely clear from looking at this function that all it does is string in,string out — someone (e.g. future us) could accidentally add non-pure functionality to it, and it wouldn't be immediately obvious.

This is exactly the kind of problem that arrow functions and ternaries help us solve.


I've tagged this pattern an Elegant Footgun™, and it defiantly makea it easy to shoot yourself in the foot.

Code-superlatives like "beautiful", "elegant" and "readable" can be very personal and depend on many factors. Personally, I find this pattern to be an elegant, readable expression of simple logic ("beautiful" would be pushing it).

I'm also emotionally prepared for the day I find myself staring at my code, trying in vain to re-understand my own cleverness. If that day comes, I hope I'll be gald I wrote this article.