How to Make Any Method Chainable in JavaScript
Tags: programming, javascript, functional
Published: 2021-12-20 16:40:37

### TLDR;
- jQuery pushed the web and JavaScript forward, but its method chaining is greatly missed
- _What if I told you_ ther is a way to bring this back? 
- This may be a representation of something that I'm not here to talk about 👀

### Why Should Anyone Care About This?
Regardless of one's experience with JavaScript, they may have heard of jQuery. During the early-to-mid 2000's, the web had reached a level of maturity allowing developers to create fluid user experiences compared to a collection of web pages. 

But, this was a tedious task given how  _bare bones_ JavaScript and web browser APIs were compared to other programming languages. Imagine not having `fetch` or `document.querySelector`. That's pretty rough right? Well, jQuery filled in *all the gaps*, and *then some*. For some, jQuery was the standard library of client-side web development. But, that was then; [JavaScript and the web has evolved](https://github.com/nefe/You-Dont-Need-jQuery).

However, with all the significant improvements enhancing JavaScript and web APIs, [jQuery's method chaining was largely left behind](https://www.w3schools.com/JQuery/jquery_chaining.asp). And because of this, [jQuery isn't leaving the tool-belt of some developers](https://stackoverflow.com/a/11503576/8861695). Can one blame them for that? jQuery provided a clean developer experience, while providing tools for building similarly clean user experiences. What's a relatively quick and painless way to bring this back?

### What's Method Chaining?
```js
$("#p1").css("color", "red").slideUp(2000).slideDown(2000);
```

Chaining methods like `.css`, `.slideUp`, and `slideDown` is highly expressive and concise. [jQuery's implementation represents a Fluent Interface](https://en.wikipedia.org/wiki/Fluent_interface#JavaScript), providing a level of [expressiveness](https://stackoverflow.com/a/2427539/8861695) where code almost reads like  plain English.

### Wouldn't Native Method Chaining be Neat?
```js
document.querySelector("#p1")
  .setCss({ transition: 'height 2s', height: '0px' })
  .setCss({ height: '100px' });
```

This could be achieved, but [one would need to know and care about  implementation details between the DOM and one's app](https://stackoverflow.com/questions/28653761/chaining-html5-classlist-api-without-jquery), which may introduce far more complexity than is required for most use cases.

### Introducing Generic Method Chaining with _The Box_

```js
Box(document.getElementById('p1'))
    .modifyContents(slideUp(2000))
    .modifyContents(slideDown(2000, '100px'));
```

The objective is to place whatever one wants inside of a _Box_. Its two methods `replaceContents` and `modifyContents` allows one to temporarily take an item outside of the _Box_, perform an action, and place it into another _Box_. 

This approach allows one to have a clear separation between what's desired (method chaining) and what one's already writing (DOM manipulation). Additionally, highly modular, and independent code is easier to compose and test.

```js
import { Box } from './box' // highly modular
import { slideUp, slideDown } from './dom' // bring your own implementation details
```

### Is this Form of Method Chaining Really Generic?

#### Numbers

```js
const four = Box(4);
const eight = four
  .replaceContents((num) => num * 2)
  .modifyContents(console.log); // 8

const ten = eight
  .replaceContents((num) => num + 2)
  .modifyContents(console.log); // 10
```

#### Arrays

```js
const nums = Box([1, 2, 3, 4, 5]);
const evens = nums
  .replaceContents((numArr) => numArr.map((x) => x + 2))
  .modifyContents(console.log) // [3, 4, 5, 6, 7]
  .replaceContents((sums) => sums.filter((x) => x % 2 === 0))
  .modifyContents(console.log); // [4, 6]
```


#### Mixed Types (Map, Array)

```js
const gteTo2 = Box(new Map([["a", 1], ["b", 2], ["c", 3]]))
  .replaceContents((table) => [...table.entries()])
  .replaceContents((arr) => arr.filter(([, value]) => value >= 2))
  .replaceContents((arr) => new Map(arr))
  .modifyContents(console.log); // Map { 'b' => 2, 'c' => 3 }
```


Yes! 

_The Box_ works with any type. Its two methods `replaceContents` and `modifyContents` have a single parameter, which is whatever item is inside of _The Box_. 

_The Box_ can contain a primitive or an object. The difference between its two methods is that `replaceContents` must return a value, and `modifyContents` does not. In other words, `replaceContents` is great for ensuring immutability.

Here's the interface for TypeScript or other languages.

```typescript
interface IBox<T> {
  replaceContents: <V>(fn: (item: T) => V) => IBox<V>;
  modifyContents: (fn: (item: T) => void) => IBox<T>;
}
```

### How Does _The Box_ compare to Fluent Interfaces?

|                          | The Box | Fluent Interfaces |
| ------------------------ | ------- | ----------------- |
| Method Chaining          | ✅      | ✅                |
| Highly Expressive        | ✅      | ✅                |
| Supports immutability    | ✅      | ✅                |
| Debugger-friendly        | ✅      | ❌                |
| Logging-friendly         | ✅      | ❌                |
| Works with any data type | ✅      | ❌                |
| Module-friendly          | ✅      | ❌                |


### Sold? Here's What _The Box_ Looks Like

```js
function Box(item) {
    const replaceContents = (fn) => Box(fn(item));
    const modifyContents = (fn) => {
        fn(item);
        return Box(item);
    };
    return { replaceContents, modifyContents };
};
```

### Wait a Minute, Is _The Box_ a [You-Know-What?](https://en.wikipedia.org/wiki/Monad_(functional_programming))

👀