Skip to content

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

@thescientist13

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

Projects

Status

🔖 Ready

Relationships

None yet

Development

No branches or pull requests

Issue actions