Use a Lit ReflexProxy to Wire Up your Web Components with StimulusReflex
Tags: stimulusreflex, lit, rails
Published: 2022-01-14 14:37:16

---
title: Use a Lit ReflexProxy to Wire Up your Web Components with StimulusReflex
published: true
date: 2022-01-13 00:00:00 UTC
tags: stimulusreflex,lit,rails
canonical_url: https://blog.minthesize.com/lit-reflex-proxy
---

 **Note** : If you’d like to try it out live first, here is a sandbox example.

{% glitch stimulus-lit-reflex-proxy %}

I like to use the [Lit](https://lit.dev/) framework to create self-contained web components that provide composable drop-in functionality. I choose them over [Stimulus](https://stimulus.hotwired.dev) controllers when I know that I will use them in the exact same way in a lot of places, and the behavior they exhibit doesn’t “leak” to other places of the DOM (in the way a Stimulus modal controller touches several places of the DOM at once). **Dependency Injection** a la HTML, so to speak. A good example is an `InlineEditElement`:

```js
import { LitElement } from "lit";
import { customElement, state, property } from "lit/decorators.js";

@customElement("inline-edit")
export default class InlineEditElement extends LitElement {
  @state()
  _armed = false;

  @property({type: String})
  value: string;

  _arm() {
    this._armed = true;
  }

  _save(value) {
    // call InlineEditReflex (?)
  }

  _handleKeydown(event) {
    if (event.key === "Enter") {
      event.preventDefault();
      this._save(event.target.value);
    }
  }

  render() {
    return this._armed
      ? html`<input
 @keydown="${this._handleKeydown}"
 type="text"
 value="${this.value}"
 />`
      : html`<span @click="${this._arm}">${this.value}</span>`;
  } 
}

```

If you look at the `render` method in the example above, it defines a _template_ whose content depends on the internal `_armed` state (React savvy folks will find this oddly familiar). By clicking on the text, `this._arm` is called and the state is switched - an `<input>` element is rendered. By hitting _Enter_ when the input element is focused, we’d like to call `_save` and trigger an `InlineEditReflex` that looks as follows.

```rb 
class InlineEditReflex < ApplicationReflex
  def save(content)    
    # persist...
  end
end

```

So much for our desires, but currently there’s no way to `stimulate` a reflex directly from within a Lit component. There’s an elegant workaround though: We can create a **transparent proxy** that mediates reflex calls back and forth between Reflex and our component:

```js 
// app/javascript/controllers/reflex_proxy_controller.js

import ApplicationController from "./application_controller";

export default class extends ApplicationController {
  initialize() {
    this.stimulateHandler = this.handleStimulate.bind(this);
  }

  connect() {
    super.connect();
    this.element.addEventListener("stimulate", this.stimulateHandler);
  }

  disconnect() {
    this.element.removeEventListener("stimulate", this.stimulateHandler);
  }

  async handleStimulate({ detail }) {
    this.stimulate(
      detail.reflex,
      this.element,
      {
        resolveLate: true,
      },
      ...(detail.args || [])
    ).then(({ payload }) => {
      if (!detail.caller) return;

      this.dispatch(detail.caller, { prefix: "afterProxy", detail: payload });
    });
  }
}

```

We implement a `handleStimulate` listener that relays stimulate calls along with any arguments to `this.stimulate`, waits for the promise to resolve and notify the caller (we’ll get to that in a minute). We can then tack this controller onto _any_ element really, like so:

```erb 
<inline-edit data-controller="reflex-proxy" id="some-sgid" value="<%= @value %>"></inline-edit>

```

What is missing is some glue code that allows our `InlineEditElement` to send events to this controller. Because this sort of functionality could be interesting for any component I create, I’ve extracted it to a [Lit mixin](https://lit.dev/docs/composition/mixins/):

```js
// app/javascript/components/mixins/reflex-proxy-mixin.js

const ReflexProxyMixin = (superClass) =>
  class extends superClass {
    afterProxy(caller, fn) {
      this.addEventListener(`afterProxy:${caller}`, fn, {
        once: true,
      });
    }

    stimulate(reflex, args, caller) {
      this.dispatchEvent(
        new CustomEvent("stimulate", {
          detail: { reflex, args, caller },
        })
      );
    }
  };

export default ReflexProxyMixin;

```

Essentially, this adds a `stimulate` method which dispatches the event of the same name, and puts the name of the reflex, any arguments, and a `caller` id into the `event.detail`. The second method it attaches to the mixed-in class is `afterProxy`, which allows to add an event listener to the element in a decoupled fashion, mimicking a lifecycle callback. If you peek into the definition of the `ReflexProxyController` above, it dispatches the respective event (with the optional `payload` from the reflex attached) back to the element.

Put together, this is what this looks like:

```js 
import { LitElement } from "lit";
import { customElement, state, property } from "lit/decorators.js";
import ReflexProxyMixin from "./mixins/reflex-proxy-mixins.js";

@customElement("inline-edit")
export default class InlineEditElement extends ReflexProxyMixin(LitElement) {
  @state()
  _saving = false;

  @state()
  _armed = false;

  @property({type: String})
  value: string;

  _arm() {
    this._armed = true;
  }

  _save(value) {
    if (this._saving) return;
    this._saving = true;

    this.stimulate("InlineEdit#save", [value], `inline-edit-${this.id}`);

    this.afterProxy(`inline-edit-${this.id}`, () => {
      this._armed = false;
      this._saving = false;
    });
  }

  _handleKeydown(event) {
    if (event.key === "Enter") {
      event.preventDefault();
      this._save(event.target.value);
    }
  }

  render() {
    return this._armed
      ? html`<input
 @keydown="${this._handleKeydown}"
 type="text"
 value="${this.value}"
 />`
      : html`<span @click="${this._arm}">${this.value}</span>`;
  } 
}

```

In `save`, we now stimulate a Reflex just like we’re used to, with a `caller` id attached so only ever the correct element gets notified:

```js 
this.stimulate("InlineEdit#save", [value], `inline-edit-${this.id}`);

```

We then install an `afterProxy` handler to clear the `_armed` and `_saving` state after the reflex has completed (Note that the `_saving` state has been added as a guard to return early if a save is currently happening, thus avoiding race conditions):

```js
this.afterProxy(`inline-edit-${this.id}`, () => {
  this._armed = false;
  this._saving = false;
});

```

And that’s it! Now you can use StimulusReflex inside every Lit component you want. A few edge cases of `this.stimulate` haven’t been covered, but this example certainly serves as a starting points for further explorations of yours 🧑‍🔬.