Skip to content

support depthful reactivity for JSX + Signals and efficient effects grouping (MVP) #256

Description

@thescientist13

Current State

Currently, WCC's JSX + Signals integration (inferredObservability) does not support reactivity deeper in the first level of a component's JSX tree. So for example, in this example

export const inferredObservability = true;

export default class Counter extends HTMLElement {
  count;
  parity;

  constructor() {
    super();
    this.count = new Signal.State(0);
    this.parity = new Signal.Computed(() => (this.count.get() % 2 === 0 ? 'even' : 'odd'));
  }

  connectedCallback() {
    this.render();
  }

  render() {
    const { count, parity } = this;

    return (
      <div>
        <span class={parity.get()}>
          You have clicked
          <span>{count.get()}</span>
          times
        </span>
      </div>
    );
  }
}

customElements.define('wcc-counter-tsx', Counter);

The nested <span>{count.get()}</span> would not be reactive and effects, templates, etc created for it.

Desired State

The current state was considered a reasonable state as far as a prototype, and so he desired state is that WCC can handle any depth of signals in a template.

Additional things to check:

  • reactive text nodes (at all levels)
  • I think we are only checking for State, I think we also need to handle Computeds (by themselves) here as well
  • should we filter the things that call .get() out against actual signals found in the constructor / JSX?
  • inline version breaks with effects
  • Better data structures for holding onto data within jsx-loader (refactor)

Additional Context

Additionally, taking the counter example from the sandbox page, we should / could improve the number of effects WCC outputs. For example, the counter example currently produces 4 effects, one for every textContent / setAttribute change.

this.$eff0 = effect(() => {
  this.$el0.textContent = SignalCounter.$$tmpl0(this.count.get(), this.parity.get());
});
this.$eff1 = effect(() => {
  this.$el0.setAttribute('class', this.parity.get());
});
this.$eff2 = effect(() => {
  this.$el1.textContent = SignalCounter.$$tmpl1(this.isLarge.get());
});
this.$eff3 = effect(() => {
  this.$el1.setAttribute('data-count', this.count.get());
});

But in reality, since all these signals / computeds are related, it would stand to reason that in this case, only one total effect would be needed?

this.$eff0 = effect(() => {
  this.$el0.textContent = SignalCounter.$$tmpl0(this.count.get(), this.parity.get());
  this.$el0.setAttribute('class', this.parity.get());
  this.$el1.textContent = SignalCounter.$$tmpl1(this.isLarge.get());
  this.$el1.setAttribute('data-count', this.count.get());
});

A post MVP enhancement would probably to start learning how effect works better and start exploring batching in the signal graph.

Metadata

Metadata

Labels

Fields

No fields configured for Feature.

Projects

Status
🔖 Ready

Relationships

None yet

Development

No branches or pull requests

Issue actions