lwc icon indicating copy to clipboard operation
lwc copied to clipboard

Reactive controllers with LWC

Open pmdartus opened this issue 3 years ago • 9 comments
trafficstars

Reactive controller is a pattern introduced with lit@2 enabling code reuse and composition between components. It has been shown that the ReactiveController interface can be adapted to other frameworks. There are also some side discussions to make this a community standard.

In essence, reactive controllers are quite similar to the LWC @wire decorator. Both are companion objects to the component and both have access to certain life-cycle hooks.

After looking at the ReactiveController interface, the main blocker on LWC is the lack of API to force a component to rerender. I have mixed feelings about exposing such API on the LightningElement as it we want developers to rely on the props reactivity to trigger rerender. This is not the only missing API required to implement the reactive controller protocol, but I think it is the most controversial one.

pmdartus avatar Dec 06 '21 15:12 pmdartus

If we allowed Reactive Controllers to opt-in to the reactivity tracking, would we need to implement requestUpdate? Based on the clock-controller.ts example in the Lit docs, if controllers somehow had reactivity tracking, we could just make requestUpdate a no-op:

this.value = new Date() // re-render happens here
this.host.requestUpdate() // no-op

nolanlawson avatar Dec 06 '21 18:12 nolanlawson

If we allowed Reactive Controllers to opt-in to the reactivity tracking, would we need to implement requestUpdate? Based on the clock-controller.ts example in the Lit docs, if controllers somehow had reactivity tracking, we could just make requestUpdate a no-op:

this.value = new Date() // re-render happens here
this.host.requestUpdate() // no-op

this.value = new Date() happens inside the controller. I'm not sure how you can trigger a re-render there without an api call?

jfu-sfdc avatar Dec 06 '21 19:12 jfu-sfdc

I'm not sure how you can trigger a re-render there without an api call?

The same way we enable deep tracking? In fact I wonder if this would already work today:

// component.js
import { LightningElement, track } from 'lwc'
export default class extends LightningElement {
  @track // deep track the controller
  clockController = new ClockController(this)
}

nolanlawson avatar Dec 06 '21 19:12 nolanlawson

@nolanlawson @pmdartus This is super useful for our AriaObserver use case, is there anything we can do to help making it happen in LWC?

jfu-sfdc avatar Dec 08 '21 18:12 jfu-sfdc

This is similar to the Plugin System (Custom Directives) that we talked about.

abdulsattar avatar Dec 09 '21 10:12 abdulsattar

The same way we enable deep tracking? In fact I wonder if this would already work today:

This approach (https://github.com/salesforce/lwc/issues/2592#issuecomment-987107184) will not work, as the track decorator only wraps arrays and objects with the null or Object.prototype prototype. In this case, the clock controller field will have ClockController prototype, which will never be observed. This restriction is in place to avoid all sort of issues related to object identity.

class Foo {
	#val = 1;

	printVal() {
  	console.log(this.#val);
  }
}

const raw = new Foo();
raw.printVal();  // val


const proxified = new Proxy(new Foo(), {});
proxified.printVal(); // 🚨 Error: Cannot read private member #val from an object whose class did not declare it

pmdartus avatar Dec 09 '21 13:12 pmdartus

certainly, this is inline with the conversation about the plugins system for LWC. Now, initially, I was proposing that the plugins will operate over the DOM element directly rather than the component instance. In Lit, they are the same thing, in LWC, they are different things but we want them to be the same thing at some point. So, I will be OK with exploring this idea with the component instead of the element, assuming that the component will give you almost all the APIs that you will ever need.

As for reactivity, I don't think those controllers should be reactive in any way, but if they need to be, then we should rely on the track decorator for those fields declared on the controller itself. In theory the track decorator should be universal, it is not at the moment.

Finally, the requestUpdate is certainly the hard cookie to swallow here because of the amount of effort and thinking that we have put onto avoiding this in LWC all together. @pmdartus can we do some research about how different frameworks are currently allowing or not such level of control? and what kind of guardrails they put in place?

caridy avatar Dec 11 '21 03:12 caridy

@caridy @pmdartus @nolanlawson It's been a few months since the last discussion. Has there been any progress on the plugin system?

jfu-sfdc avatar Apr 04 '22 21:04 jfu-sfdc

@caridy @pmdartus @nolanlawson It's been a few months since the last discussion. Has there been any progress on the plugin system?

No, no that I'm aware of.

caridy avatar Apr 05 '22 04:04 caridy