You don't know the classNames library
Tags: react, javascript, webdev
Published: 2021-03-10 19:20:07

Let me contradict the very title of this post from the start by saying that you probably know the `classNames` library very well.

This tiny [open-source](https://github.com/JedWatson/classnames) library, originally created by [JedWatson](https://github.com/JedWatson), is nothing but a versatile string *“concatenator.”* Currently sitting at over **6M weekly downloads**, it rose to popularity alongside React - the UI library that it’s most commonly used with.
<figure>
<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xmxdqzspmwqqn37zeyk0.png" alt="classNames and React popularity growth"/>
<figcaption>classNames and React popularity growth</figcaption>
</figure>
As the name implies, it’s primarily meant for dealing with CSS classNames (very common in React and any other **JSX-based** UI framework), although you can use it for any kind of string concatenation.

# The novelty of tiny NPM packages

But you most likely know it all. After all, given classNames and React popularity, there’s a high chance that you’ve used it before. But yeah, about this popularity.

It’s not uncommon to see tiny packages have insanely high download stats on NPM. You most likely [have heard the stories](https://www.google.com/search?q=tiny%20package%20breaks%20npm) of NPM packages with even less than 20 lines of code, breaking the internet because of the slightest change. Take [is-promise](https://www.npmjs.com/package/is-promise) for example - sitting at around 10M weekly downloads (mainly from its highly-popular dependents) - with its largest CJS version measuring 5 lines of code (LOCs):
```javascript
module.exports = isPromise;
module.exports.default = isPromise;

function isPromise(obj) {
  return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';
}
```
Now, such dependence might seem scary, and rightfully so. The jokes about black-hole `node_modules` folders are true for a reason. Even though you might not depend on such packages directly, packages that you depend on might do so, one, or more likely, multiple levels deep.

classNames isn’t exactly that tiny, with around 50 LOCs at its “core” version and between 200 - 300 in total (all versions + TypeScript typings). This is what I consider a “sane” threshold for package dependence. Besides, the library is very stable and has proven itself over the years.

# Classnames syntax

The usefulness of the classNames library comes from its syntax. All it is is a single function, taking different types of values as arguments and spitting out a single string based on them.

The README does a great job of showcasing this versatility:
```javascript
import classNames from "classnames";

const arr = ["b", { c: true, d: false }];
const buttonType = "primary";

classNames("foo", "bar"); // => "foo bar"
classNames("foo", { bar: true }); // => "foo bar"
classNames({ "foo-bar": true }); // => "foo-bar"
classNames({ "foo-bar": false }); // => ""
classNames({ foo: true }, { bar: true }); // => "foo bar"
classNames({ foo: true, bar: true }); // => "foo bar"

// lots of arguments of various types
classNames("foo", { bar: true, duck: false }, "baz", { quux: true }); // => "foo bar baz quux"

// other falsy values are just ignored
classNames(null, false, "bar", undefined, 0, 1, { baz: null }, ""); // => "bar 1"

classNames("a", arr); // => "a b c"

classNames({ [`btn-${buttonType}`]: true }); // => "btn-primary"
```
This library's simplicity and versatility is probably something that you don’t really think about - you just use it, making it run thousands or even millions of times throughout your projects.

## Performance

It might cause some concerns about performance. The author is aware of that, which is clearly stated in the README:

> We take the stability and performance of this package seriously, because it is run millions of times a day in browsers all around the world. Updates are thoroughly reviewed for performance impacts before being released, and we have a comprehensive test suite.

However, it’s clear that a function call will never be faster than nothing but a simple string, and even though it seems like a micro-optimization, it’s also a code readability concern.
```javascript
// pointless
classNames("foo", "bar"); // => "foo bar"
```
So, it’s important not to get into a loop-hole and know when and how to use the library responsibly. In simpler cases, see if a plain string, ES6 template literal, or a conditional operator won’t do the job. Don’t waste performance, but also don’t over-optimize.
```javascript
// make it better
classNames("foo", "bar"); // => "foo bar"
classNames(condition ? "foo" : "bar"); // => condition ? "foo" : "bar"
classNames(foo, bar); // => `${foo} ${bar}`
```
## Versatility

Apart from using classNames only when necessary, there’s still a lot to gain from using it **properly**. The biggest factor here is the **versatility**, which can often cause you to go with the sub-optimal way for a given scenario.

As noticed above, you can supply as many arguments as you want from which falsy values are ignored, strings are joined, arrays recursively flattened and processed, and objects’ keys joined if their values are truthy.

You can use these properties not necessarily to improve the **performance** but rather the readability and *“writing comfort”* of your code (aka *“development experience”*). As for some advice:
```javascript
// use separate strings for base classes
classNames("foo", { bar: condition } /*...*/);
// provide multiple arguments instead of an array
classNames(
  "foo",
  {
    /*...*/
  },
  condition ? "a" : "b"
);
/* use conditional operator for switching between classes
 and object or "AND" operator for turning a single one on and off */
classNames(
  condition ? "a" : "b",
  { c: secondCondition },
  thirdCondition && "d"
);
```
These are just a few tips from the top of my mind, which I personally use. It’s common to use an unnecessary array or to put base classes into an object property name with ugly `true` on its right, or to switch between sets of classes through an object with property values going like `condition`, `!condition`. None of those issues are particularly disturbing, but it’s worth remembering that there’s some room for improvement.

# Classnames alternate versions

You might not have known, but classNames comes with 2 alternate versions of itself. Both serve pretty the same general purpose but also provide additional features.

## dedupe

As the name implies, the *dedupe* version deals with duplicates in the generated string. It removes duplicate substrings and only respects the last provided setting for the particular substring.
```javascript
import classNames from "classnames/dedupe";

classNames("foo", "foo", "bar"); // => 'foo bar'
classNames("foo", { foo: false, bar: true }); // => 'bar'
```
Due to the complexity of deduping, this version is said to be **5x slower**. Because of that, I don’t really recommend you use it for your `className`s unless you’ve got a really specific reason. It still can be useful for generic string concatenation, though.

## bind

The second version is targeted towards users of **CSS Modules**. When importing your object of CSS classes, this version allows you to “bind” them so that you can reference them by their custom name instead of the real one.
```javascript
import classNames from "classnames/bind";

const styles = {
  foo: "abc",
  bar: "def",
  baz: "xyz",
};
const cx = classNames.bind(styles);

cx("foo", ["bar"], { baz: true });
```
This method can save you some typing - no need to always access the properties from the imported object. However, it does introduce some additional performance loss (although really tiny), can confuse new-comers, requires creating a new “instance” of classNames function, and will cause you to lose potential autocompletion and other kinds of editor support ([included TypeScript typings](https://github.com/JedWatson/classnames/blob/master/bind.d.ts) are very generic).

With that said, you should only use this version when you have a lot, and I mean **a lot**, of CSS module-imported classes to deal with (which you shouldn’t, by the way, it’s not “ergonomic”)

# Just use [clsx](https://www.npmjs.com/package/clsx)

Now, as a free tip to **[at least 2x](https://github.com/lukeed/clsx/tree/master/bench)** [the performance](https://github.com/lukeed/clsx/tree/master/bench) of the thousands of `classNames` calls you probably make, just switch to `clsx`. It’s a similar library to `classNames`, but a bit more fresh-y, and with even fewer LOCs.

The API is identical to the `classNames` one, but without the additional versions (which you probably don’t need anyway). And while the performance gains might not be noticeable, it still means that there’s speed left on the table, and the multitude of calls can quickly add up to something more.

# Thoughts?

It feels a bit crazy to write a whole article about a one-function utility library. However, given how popular `classNames` is, how often it’s used and how almost unnoticeable it is, I think it deserved a bit in-depth look. Such small libraries and open-source tools are what’s powering today’s Web, and thus it’s important to keep try of your dependencies, know them well, and know how to optimize them.

Anyway, that’s been it! I hope you enjoyed this crazy ride and maybe - just maybe - learned something new today. If so, let me know down in the comments. Be sure to **follow me** on [Twitter](https://twitter.com/areknawo), [Facebook](https://www.facebook.com/areknawoblog), or through my [newsletter](https://areknawo.com#newsletter) for more bonkers web devs stories like this one and some more sane ones! Oh, and maybe **start writing your own** with [CodeWrite](https://codewrite.io)!

Thanks for reading, and happy class-naming.