A Little Bit of JavaScript: classnames
Tags: javascript, react
Published: 2019-12-24 18:19:56

The [`classnames`](https://github.com/JedWatson/classnames) library is something I use nearly every day as a React developer. It is "a simple JavaScript utility for conditionally joining classNames together."

Here is a minimal example from their docs of how it is used:

```javascript
var classNames = require('classnames');
classNames('foo', 'bar'); // => 'foo bar'
```

For 99% of my use cases, it allows me to do the following things:

- combine some static CSS class value with some computed value
- combine an incoming classNames prop with other in-component classNames
- conditionally include CSS class values through an object literal

The library likely does a few other more specific things and gracefully handles a variety of edge cases, but I bet we can get the majority of the behavior we need with just a little bit of JavaScript.

But first, let's look at a more real-world example of JSX that we want to support:

```javascript
import React from "react";
import cx from "classnames";

export default function Button(props) {
  const { size, className, disabled, ...rest } = props;
  const sizeClassName = `btn-${size}`;

  return (
    <button
      className={cx("btn", sizeClassName, className, {
        ["btn-disabled"]: disabled
      })}
    >
      {/* ... */}
    </button>
  );
};
```

Our focus will be on the `cx` (my preferred shorthand when importing `classnames`) value we are computing. Here is an example of what we might expect:

```javascript
const size = "medium";
const className = "text-bold";
const disabled = true;
    
cx("btn", sizeClassName, className, {
  ["btn-disabled"]: disabled }
);
//=> "btn btn-medium text-bold btn-disabled"
```

Here is a little JavaScript to make this utility ourselves:

```javascript
function cx(...classnames) {
  return classnames
    .map(item => {
      if (typeof item === "string") {
        return item;
      }
      if (typeof item === "object") {
        return Object.keys(item)
          .map(key => {
            return item[key] ? key : void 0;
          })
          .join(" ");
      }
      return void 0;
    })
    .join(" ");
}
```

I'll explain a bit more below, but feel free to check out the [interactive example](https://codesandbox.io/s/patient-bash-z8flz) as well.

After `prettier` does its thing, this comes out to 17 lines of code. Nevertheless, there is a lot going on here, so let's look at it piece by piece.

```javascript
function cx(...classnames) {
```

Use of the [spread operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) gathers one or more arguments into an array referenced by the `classnames` variable.

```javascript
return classnames
  .map(item => { /* logic to process args here ... */ })
  .join(" ");
```

We then map over each argument in `classnames`. Some logic that we will look at in a second will determine each string part that will make up the resulting `className` value. These are joined together with spaces between and returned.

Now for the guts of the map function:

```javascript
(item) => {
  if (typeof item === "string") {
    return item;
  }
  if (typeof item === "object") {
    /* handle object literals here ... */
  }
  return void 0;
}
```

The simple case is if an argument is a string; we'll just return it as is. If we encounter an object literal (e.g. `{ cats: true }`), then we'll have to do some special processing of that. Anything else we are choosing to ignore, so we'll return `void 0` (which is `undefined`).

Here is how we process an object literal argument:

```javascript
if (typeof item === "object") {
  return Object.keys(item)
    .map(key => {
      return item[key] ? key : void 0;
    })
    .join(" ");
}
```

Mapping over each key-value pair in the object, we include the `key` if it is paired with a truthy value, otherwise we return `undefined`. The result of this mapping is joined together with a single space as the delimiter. This mapped and joined string will then get joined into the string that ultimately is returned.

Now that we've looked at all the parts, let's look at the whole thing together again:

```javascript
function cx(...classnames) {
  return classnames
    .map(item => {
      if (typeof item === "string") {
        return item;
      }
      if (typeof item === "object") {
        return Object.keys(item)
          .map(key => {
            return item[key] ? key : void 0;
          })
          .join(" ");
      }
      return void 0;
    })
    .join(" ");
}
```

You may not be looking to replace your `classnames` dependency with a hand-rolled version anytime soon, but it's nice to remember how far you can get with just a little bit of JavaScript.

Happy Holidays 🎄