Wielding replace with all its power
Tags: javascript, todayilearned, devtips
Published: 2019-09-03 15:51:12

[Regular expressions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) can be a great help when we need to analyse or transform user input or other strings the precise content of which we don't know until runtime, especially so if we need to work with complex expressions. `String`'s method `.replace()` is very versatile. We can use it to look for a pattern in the form of a string or a regular expression within a string and return a new one with the pattern replaced.

Up until here, here, this sounds very good but very bland. The exciting part come when one realises that we can pass a function as a parameter to act on each match.

## How does that work?

```javascript
const newStr = str.replace(pattern, replacement)
```

This is the method's basic syntax. The `pattern` will be either a string or a regular expression. The `replacement` will be a string or a function. If it is a function, it can take several arguments:

- `match` the first argument is the entire match in the string
- `group` next, there will be one argument for each [captured group](https://www.regular-expressions.info/brackets.html)
- `offset` the offset of the match within the string (ie, the index in the string where the match starts)
- `string` the whole string

## What is it useful for?

A while back, I wrote an article explaining [how lookaheads helped me turn camelCase into kebab-case](https://dev.to/savagepixie/lookaheads-in-javascript-5d4j). Let us imagine that I want to do just the opposite this time, turn kebab-case into camelCase. One way to do it would be this.

```javascript
const toCamelCase = str => str.split("-")
          .map((x, i) => i == 0 ? x : x.slice(0,1).toUpperCase() + x.slice(1))
          .join('')

toCamelCase("some-stuff-written-here") //returns someStuffWrittenHere
```

If I wanted, instead of six methods and a callback function, I could just use two methods and a callback function.

```javascript
const toCamelCase = str => {
  const regEx = /-(\w)/g
  return str.replace(regEx, (_, letter) => letter.toUpperCase())
}

toCamelCase("some-stuff-written-here") //returns someStuffWrittenHere
```

The result is the same, but the statement much shorter. _I think it is important to note, however, that some not very conclusive tests seem to indicate that the first function is marginally faster for small amounts of text._

## Doing some more complex stuff

Now let's imagine that we have a text that includes an expression for a dice roll. We don't know how many dice were rolled or how many sides those dice had until our website gets the data. But we know that the expression will be in the form of `xdy` where `x` indicates the amount of dice rolled and `y` how many sides those dice had. We want a function that returns a new string with the result of the dice roll in it.

The first thing that we need is a function that simulates a dice roll. Let's make something simple.

```javascript
const rollDice = (dice, sides) => {
  let result = 0
  for (let i = 0; i < dice; i++) result += Math.ceil(Math.random() * sides)
  return result
}
```

With that out of the way, let's dive into the function that will add the result next to each dice roll:

```javascript
const addResult = (string, roller) => {
  const regEx = /(\d+)d(\d+)/g
  return string.replace(regEx, (roll, dice, sides) => `${roll} [${roller(dice, sides)}]`)
}

const string = "The first dice roll is 2d6 and the second one is 3d8."

addResult(string, rollDice)
//Returns something like this: "The first dice roll is 2d6 [7] and the second one is 3d8 [17]."
```

Here we've created a function that takes a string and another function to roll dice. Inside the function, we call `.replace()` to find dice roll expressions in the string and execute our dice roller for each match, so that the resulting new string includes the result of the roll after the expression.

In this case, our regular expression has two captured groups, each of them consisting of one or more digits (`(\d+)`). When we call our callback function, we name the first group `dice` and the second `sides`. The whole matching expression will be captured in the argument `roll`. We don't need any of the other arguments that it can take, so we simply omit them.

## When it gets really fun

But we can even use `.replace()` without actually replacing anything in the string. Consider the following example.

```javascript
const addResult = (string, roller) => {
  const regEx = /(\d+)d(\d+)/g
  const newArr = []
  string.replace(regEx, (roll, dice, sides) => newArr.push({
      roll: roll, 
      result: roller(dice, sides) 
    }))
  return newArr
}
```

This time, our `addResult()` returns an array of objects where it has stored all the dice roll expressions and their calculated result without actually doing anything to the original string. This technique is useful when we want to gather data from a string, but we aren't particularly interested in changing it.

## Conclusion

Because it can take a function as a parameter, `.replace()` can be a very useful method when we need to manipulate a text in complex ways or we simply want to gather information from it.

If you want to learn more about it, you can always check [MDN web docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace). Personally, I also found the chapter dedicated to regular expressions in [_Secrets of the JavaScript Ninja_](https://www.manning.com/books/secrets-of-the-javascript-ninja) very enlightening.