jorgenfb
jorgenfb

Reputation: 2237

How to mutate a property within a stenciljs component without triggering watch

I have a component with a viewport property. I want to listen for changes to this property, do some calculations and reflect a possibly changed value back to the property. My first attempt looked something like this:

class MyComponent {
  @Prop()
  viewport: ViewportData

  @Watch('viewport)
  viewportChanged(newValue: ViewportData, oldValue:ViewportData) {
    ... do some calculations

    // Reflect value back as property
    this.viewport = computedViewport;
  }
}

This results in a stack overflow because reflecting the value back triggers another call to the watch function. I could prevent it by having a flag saying if this is an internal change or not. Something like this:

class MyComponent {
  internalViewportChange = false;

  @Prop()
  viewport: ViewportData

  @Watch('viewport)
  viewportChanged(newValue: ViewportData, oldValue:ViewportData) {
    if (this.internalViewportChange) {
      this.internalViewportChange = false;
      return;
    }

    ... do some calculations

    // Reflect value back as property
    this.internalViewportChange = true;
    this.viewport = computedViewport;
  }
}

I don't like this approach. And is looking for something better. This problem could normally be solved by using getters and setters and a private variable keeping the actual state:

class MyComponent {
  private _viewport: ViewportData

  get viewport() {
    return this._viewport;
  }

  set viewport() {
    ... do some calculations

    // Reflect value back as property
    this.viewport = computedViewport;
  }
}

However, using Stenciljs the getters and setters are autogenerated. Any good ideas?

Upvotes: 2

Views: 1732

Answers (1)

matthewsteele
matthewsteele

Reputation: 1862

I'd probably break the two-way prop setting, and create a unidirectional data flow, and emit events instead. Something like:

class MyComponent
  @Event() viewportChanged: EventEmitter;
  @Prop() viewport: ViewportData;
  @State() _computedViewport: ViewportData;

  @Watch('viewport') onViewportChanged(newValue) {
    // do calculations
    this._computedViewport = computedViewport;
    this.viewportChanged.emit(this._computedViewport);
  }

Internally, you'd only work on _computedViewport, and the public viewportProp is only there for users to update themselves. Ostensibly you could also expose a @Method() that does the same thing.

Upvotes: 1

Related Questions