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 } = {}) =>
fullUrl
.replace(/(https?:\/\/)/i, removeProtocol ? '' : '$1')
.replace(/(www\.)/i, removeWww ? '' : '$1')
How
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
''
else
_match_ /* replace it with itself */
Why
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.
When
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.
Top comments (1)
Btw, I think this little auxiliary function is a bit easier to read: