Skip to content

Possible feature: mobx-like magic #2

Description

@43081j

Mobx heavily uses "magic" (i.e. hides away most of the reactions/observations by default and 'figures them out').

Not everyone is a fan of this but it has proven to be fairly popular, so i had a go at doing similar with atom.

Here's what i have:

Usage

export class MyElement extends AtomLitElement {
  render() {
    return html`
      <button @click="${() => setCount(old => old - 1)}">-</button>
      <span>${count.getState()}</span>
      <button @click="${() => setCount(old => old + 1)}">+</button>
    `;
  }
}

Implementation

import {LitElement, html, PropertyValues} from 'lit-element';

// NOTE: atoms isn't currently exported
import {atom, atoms} from "@klaxon/atom";

const [count, setCount] = atom({
  key: 'count',
  default: 0
});

// Truly excellent name
class AtomicReactor {
  public callback: () => void;
  protected _accessed: Set<string> = new Set();

  public constructor(callback: () => void) {
    this.callback = callback;
  }

  public track(): () => void {
    const originalGet = atoms.get;

    // NOTE: overwriting a method like this always feels like hackery
    // but the only other way i suppose is to have atom itself have a concept
    // of notifying of accesses (e.g. an event fired when one is accessed, but more
    // performant than that).
    atoms.get = (key: string) => {
      this._accessed.add(key);
      return originalGet.call(atoms, key);
    };

    return () => {
      atoms.get = originalGet;

      for (const [key, atom] of atoms.entries()) {
        if (this._accessed.has(key)) {
          // NOTE: needs to be removed at some point so we don't fill
          // memory with event listeners over time
          atom.addEventListener(key, () => {
            this.callback();
          });
        }
      }
    };
  }
}

const reactor = Symbol();

class AtomLitElement extends LitElement {
  protected [reactor]?: AtomicReactor;

  public connectedCallback(): void {
    super.connectedCallback();

    this[reactor] = new AtomicReactor(() => {
      this.requestUpdate();
    });
  }

  public disconnectedCallback(): void {
    super.disconnectedCallback();

    // NOTE: probably needs a this[reactor].dispose() which
    // removes all those previously mentioned listeners
    this[reactor] = undefined;
  }

  protected update(change: PropertyValues): void {
    const reactorInstance = this[reactor];
    if (reactorInstance) {
      const disposer = reactorInstance.track();
      super.update(change);
      disposer();
    } else {
      super.update(change);
    }
   }
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions