JavaScript array methods are super useful, and learning how to use them can really help improve the readability of your code. This is the second part of a series on JavaScript array functions, where I dive into examples from real, production codebases. Today's function is reduce
, which (for me at least) was the most abstract one of the three. By virtue of its abstractness, however, it's also the most powerful. In fact, it's possible to do the jobs of the other two using just reduce
alone! (Even though you shouldn't. Definitely, absolutely, do not do this.)
Much like the map
function, reduce
is called on an array, and takes in two parameters: a callback, and an initial value. However, the callback looks a little different than the one in map
— instead of taking one parameter, it takes two: an accumulator, and the current element. This gets to the very heart of the reduce
function: starting with the initial value, it iterates over every element in the array, returning the result of the callback function as the accumulator to the next iteration of the loop. If that sounds confusing, don't worry. That's what the examples are for!
A trivial example
Before we get into code, I want to really drill down into what reduce
does. An analogy that I found really helpful goes as follows:
Imagine a line (i.e. array) of people. You want to find the sum of their ages; that is, you want to reduce your array of people into a single number — their combined age. To do that, you'd probably use a calculator app and go down the line one person at a time, adding to your total as you go. That's exactly what the reduce
function does — the initial value is 0, the accumulator is the running total in your calculator, and the current element is the person you're currently in front of.
With that in mind, let's see a simple example using the same sort of analogy:
const arrayOfPeople = [
{
name: 'John Doe',
age: 21
},
{
name: 'Mary Sue',
age: 34
},
{
name: 'Gary Stu',
age: 43
}
];
const combinedAge = arrayOfPeople.reduce((acc, curr) => acc + curr.age, 0);
console.log(combinedAge); // => 98
To visualize how this works, use the same line of people analogy. Imagine you have a calculator, and you need to count the combined ages of these three people. You'd start off with 0 in your calculator — that's the initial value. Then you'd go up to John Doe, ask them their age, and add that to the value in your calculator. 0 plus 21 gives 21, so that's the running total so far. Then you'd go up to Mary Sue and ask them for their age. They say 34, so you add that to your calculator; 21 plus 34 gives 55, so now that's your running total. Finally, you'd go up to Gary Stu, ask them their age, and add that in. 55 plus 43 gives 98 — and that's exactly what reduce
returns.
Now that we have that under our belt, let's look at some real-life examples:
Converting HTML nodes to strings
In this example, I was writing a feature for my blog that allowed the user to share a post to dev.to. I needed to select a bunch of tag elements on my page and convert them into a comma-separated string as part of the post frontmatter. This is the perfect use case for reduce
; it takes an array of objects and squashes or reduces them down into a single value. Here's how I did it:
const tagString = ` tags:${Array.from(document.querySelectorAll(".tags span.tag")).reduce((acc, curr) => {
return acc + (acc == "" ? "" : ", ") + curr.textContent;
}, "")}` ;
Don't be fooled by the complicated looking ternary operator — it's only there to make sure that the first element doesn't have a comma before it. Otherwise, all the reduce
function is doing is adding commas between the text contents of all of the tags.
Before we move on, a good question is why I couldn't use a function like join
to do this. The answer is that you can't join an array of HTML nodes — you need to get their textContent
property to see what they contain. What I could've done instead is map
each element of the array to their textContent
and then join
them, but one method is much better than two. Hence, the reduce
function. On an unrelated note, if you'd like to see some examples of the map
function being used, be sure to check out my article.
With that said, let's look at another example:
Formatting comments
I recently implemented a comment section on my blog, and as part of that I wanted users to be able to apply basic formatting to their comments. This included bold, italics, code, and linebreaks. Because I didn't want to use any external libraries or parsers, however, I had to convert raw Markdown data into safe HTML elements. To do this, I needed to separate the input data by line, escape any HTML, and then run a custom Markdown converter on each line.
That seems like a lot, but it's the perfect job for a workhorse like reduce. I can perform the HTML escaping in the callback, and extract any useful code like the Markdown parsing to an external function. Here's what I ended up with:
return body.split('\n').reduce((acc, curr) => {
let text = document.createTextNode(curr);
let p = document.createElement('p');
p.appendChild(text);
if (curr.trim().length === 0) return acc;
return acc + (acc === "" ? "" : '</p><p class="comment-body">') + safeMarkdownToHTML(p.innerHTML);
}, "");
The first few lines are just a way to leverage the browser's built-in HTML escaping with the createTextNode
function. After that, I use a ternary operator (again!) to make sure that the first element doesn't have any unwanted content appended before it. Finally, I return the results of the (recursive) Markdown parsing function. While it may seem like a lot at first, by breaking it down into pieces, we can see how the final product is constructed. In this case, reduce
serves as one tool among many to achieve this goal. By the way, let me know down in the comments if you'd like to see a post on parsing Markdown — it's a great introduction to recursion and string manipulation.
Let's take a look at one final example:
Making JavaScript effects accessible
On several pages on my website — my home page, blog page, and design page, for example — I use a typewriter effect as a bit of eye-candy. While cool-looking (I'm quite proud of the effect, to be honest), it's important to recognize that not everybody sees the internet in the same way. This effect in particular is quite inaccessible to people who use screen readers, so I had to find a way to convert the array of disparate words into one long phrase that could be read out via the aria-label
attribute.
To do this concatenation, I once again reached for reduce
. Because of the nature of the function, I was able to make a logical, grammatically correct sentence that would make sense when read out. Here's what that reduce
function looked like in context:
let t = new Typewriter(
el,
el.dataset.speed,
el.dataset.pause,
JSON.parse(el.dataset.text),
[...new Set(JSON.parse(el.dataset.text))]
.reduce((acc,curr) => acc + ", and " + curr.trim()), "")
);
Super simple, super sweet — all I had to do was add ", and"
between each element of the array. Again, I didn't end up using join
because I had to call trim
on each piece of text. Using reduce
allows the array to be transformed and mutated while it's being collected, which is perfect for this use case. By the way, if you're interested in learning more about accessibility on the web, and in particular with JavaScript, be sure to subscribe to my mailing list — I'm going to have a lot of posts dedicated to the topic in the near to medium future.
Wrapping it up
I hope these examples gave you an idea of how the reduce
function is really used in a codebase, and how it can help make code more readable and versatile. Let me know down in the comments if you have any interesting uses for the reduce
function, and keep an eye out for the final post in the series!
As always, don't forget to follow me for more content like this. I'm currently writing on dev.to and Medium, and your support on either platform would be very much appreciated. I also have a membership set up, where you can get early previews of articles and exclusive access to a whole bunch of resources. Also, if you've particularly enjoyed this post, consider supporting me by buying me a coffee. Until next time!
Top comments (2)
Great post! Very clear and well detailed, one thing I noticed is that in the first example the reduce function is not targeting arrayOfPeople but arr.... And the curr value is referring to each object of the array so in order to work it has to be acc + curr.age. I know it was an example to illustrate the concept, but for the reader that never has worked with this it might be confusing...
Thanks for the effort to help this community grow, and keep up the good work!
Thanks so much for catching that! I updated it in the article. I'm glad you enjoyed the post, and thanks for the support!