You probably want some parts of Lodash

note: If you're someone who already uses a relatively functional programming style, this post is going to be pretty boring!

You might not need Lodash, but that doesn't mean you don't want some subset of its functions in a utility library. Lodash doesn't tree-shake well, so you might not want to use Lodash's actual library, but I think versions of some of its utility functions—_.groupBy, _.keyBy, _.mapValues, _.orderBy, and _.uniqBy—are essential for writing clean, expressive code.

I think example code is the best way of showing why these particular methods can make code more readable. Let's imagine that we're trying to take a list of classes and a list of teachers, organize those classes by grade level, and then print class names out in descending order of grade level with the name of the owner in parens and the number of co-teachers listed out:

### Grade:5

- Algebra [Mrs. Sprout] (1 co-teachers)
- Chemistry [Mr. Xvim Chao] (2 co-teachers)
- Geometry [Ms. Frizzle] (1 co-teachers)

### Grade:4

...

We have the following data:

Let's assume that teachers and classes have enough rows that we want to avoid using Array.find. If I were trying to write reasonable code without helper functions for that, it might look something like the following:

const teachersById = {};
for (const teacher of teachers) {
  teachersById[teacher._id] = teacher;
}

const classesByGrade = {};
for (const c of classes) {
  classesByGrade[c.grade] ??= [];
  classesByGrade[c.grade].push(c);
}

for (const classes of Object.values(classesByGrade)) {
  classes.sort((a, b) => a.name < b.name ? -1 : 1); // sorting classes by name alphabetically
}

const classGrades = Object.keys(classesByGrade);
classGrades.sort((a, b) => b.grade - a.grade);

const output = [];
for (const grade of classGrades) {
  output.push(`### Grade:${grade}`);
  output.push("");

  output.push(...classesByGrade[grade].map((class) => `- ${c.name} [${teachersById[c.classOwnerId].name}] (${c.coTeacherIds.length} co-teachers)`);
  output.push("");
}

return output.join("\n");

There are a few things I don't like about this version of the code:

  1. I can never remember how .sort works, so I'm not able to read (or write) the code and tell whether I'm ordering things correctly.
  2. It's relatively verbose. While each for loop is only doing a single thing, you have to read the code to tell what that thing actually is.

Let's compare this to a more functional style that uses Lodash:

const teachersById = _.keyBy(teachers, "id");
const classesByGrade = _.groupBy(classes, "grade");
const orderedClassesByGrade = _.mapValues(classesByGrade, (classes) => {
  return _.orderBy(classes, ["name"], ["asc"]);
});

const gradeOrder = _.orderBy(
  Object.keys(orderedClassesByGrade), [(g) => g], ["desc"]
);

const output = gradeOrder.flatMap((grade) => {
  return [
    `### Grade:${grade}`,
    "",
    ...orderedClassesByGrade[grade].map((c) => {
      return `- ${c.name} [${teachersById[c.classOwnerId].name}] (${c.coTeacherIds.length} co-teachers)`;
    }),
    ""
  ];
});

return output.join("\n)

If you're not used to these particular Lodash methods, this probably looks worse to you! I personally find it easier to read:

You might not like either of these implementations! I'm not arguing that either implementation is the "right way" to tackle this particular problem, but I'm hoping that the comparison between them is useful to illustrate why a person might want those particular utility functions if they're regularly re-shaping data.

Still... you probably don't need all of Lodash, especially if you're working on the frontend. It's a relatively large dependency, and it doesn't tree-shake well. The methods that I personally think are essential are:

Depending in the problem domain that you're working in, different lodash methods might prove to be the essential ones! But for whatever problem domain you're working in, I bet there's a set of standard utility functions that would make your code easier to read and work with. The list of Lodash functions is a good starting point.