From a6231fd010483c49251ceb893c5affdd0d91fc18 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Fri, 24 May 2024 09:51:05 -0400 Subject: [PATCH] Deprecating $reactTo --- README.md | 85 +------- .../lwc/contactInfoForm/contactInfoForm.js | 11 +- .../lwc/countTracker/countTracker.html | 2 - .../counter/lwc/countTracker/countTracker.js | 10 +- force-app/lwc/signals/core.js | 182 +++++++++--------- src/lwc/signals/core.ts | 13 +- 6 files changed, 99 insertions(+), 204 deletions(-) diff --git a/README.md b/README.md index 8255b74..013a89a 100644 --- a/README.md +++ b/README.md @@ -44,10 +44,10 @@ This can be cumbersome when you have a lot of components that need to share stat - You have to make sure manage subscriptions and unsubscriptions to events An alternative is to use the `wire` service to get the data from the server and let the framework handle the caching -for you, but this only works for data that is signalsd in the server, and still forces you to implement a lot of +for you, but this only works for data that is stored in the server, and still forces you to implement a lot of boilerplate code to manage each wire adapter for each component. -`LWC Signals` provides a simple way to create reactive data signalss that can be used to share state between components +`LWC Signals` provides a simple way to create reactive data signals that can be used to share state between components without the need to broadcast messages or manage subscriptions and wires. ## Creating a signals @@ -104,76 +104,9 @@ export default class Counter extends LightningElement { ## Reacting to changes -### `$reactTo` - -To have your components automatically react to changes in the signals, you can use the `$reactTo` -function to create a reactive value that will update whenever the signals changes. - -Let's create another component that displays the counter value and automatically updates when the counter changes. - -```html - - -``` - -```javascript -// display.js -import { LightningElement } from "lwc"; -import { $reactTo } from "c/signals"; -import { counter } from "c/counter-signals"; - -export default class Display extends LightningElement { - get counter() { - return $reactTo(counter); - } -} -``` - -> ❗`$reactTo` should be used inside a getter to make sure that the UI updates when the value changes. -> Keep reading to see other ways to react to changes in the signals. - -
- Counter Example -
- ---- - ### `$computed` -You can also use the `$computed` function to create a reactive value that depends on the signals. -The difference between `$reactTo` and `$computed` is that `$computed` allows you return a derived computed signals (with -the difference of it being read only) -from the original, or multiple signals. - -```javascript -// display.js -import { LightningElement } from "lwc"; -import { $computed } from "c/signals"; -import { counter } from "c/counter-signals"; - -export default class Display extends LightningElement { - get counterMultiplied() { - return $computed(() => counter.value * 2).value; - } -} -``` - ---- - -Notice that in the examples we have been using getters to react to value changes. This is because LWC's reactive system -can automatically detect changes in getters for simple values and updates the UI accordingly, which makes for a cleaner -developer experience -and easier to reason about the code. - -But there are cases where we need to use a property in case of a getter, for example when computing values into a -complex object, in which case the LWC -framework won't update the UI automatically. For cases like this, you can leverage the -`$computed` function to create a reactive property that will update whenever the signals changes. - -> See the (Reacting to multiple signals)[#reacting-to-multiple-signals] section for an example where we need -> to use a property instead of a getter. +You the `$computed` function to create a reactive value that depends on the signals. ```javascript // display.js @@ -247,17 +180,13 @@ export const contactName = $signal("John Doe"); ```javascript // contactInfoForm.js import { LightningElement } from "lwc"; -import { $reactTo } from "c/signals"; +import { $computed } from "c/signals"; import { accountName, contactName } from "c/demoSignalss"; export default class ContactInfoForm extends LightningElement { - get accountName() { - return $reactTo(accountName); - } + accountName = $computed(() => (this.accountName = accountName.value)).value; - get contactName() { - return $reactTo(contactName); - } + contactName = $computed(() => (this.contactName = contactName.value)).value; handleAccountNameChange(event) { accountName.value = event.target.value; @@ -519,7 +448,7 @@ export default class AccountPicker extends LightningElement { Notice how we are using a `@wire` service to fetch the accounts from the server and populate the picklist. This is because in this case we don't care about sharing that data with other components, and we only need it once. Be -pragmatic about when to use signalss and when not to. Opt to use the base Salesforce services when you only need the +pragmatic about when to use signals and when not to. Opt to use the base Salesforce services when you only need the data in a single component. diff --git a/examples/computed-from-multiple-signals/lwc/contactInfoForm/contactInfoForm.js b/examples/computed-from-multiple-signals/lwc/contactInfoForm/contactInfoForm.js index 409a47b..a801bc2 100644 --- a/examples/computed-from-multiple-signals/lwc/contactInfoForm/contactInfoForm.js +++ b/examples/computed-from-multiple-signals/lwc/contactInfoForm/contactInfoForm.js @@ -1,15 +1,10 @@ import { LightningElement } from "lwc"; -import { $reactTo } from "c/signals"; +import { $computed } from "c/signals"; import { accountName, contactName } from "c/demoSignals"; export default class ContactInfoForm extends LightningElement { - get accountName() { - return $reactTo(accountName); - } - - get contactName() { - return $reactTo(contactName); - } + accountName = $computed(() => (this.accountName = accountName.value)).value; + contactName = $computed(() => (this.contactName = contactName.value)).value; handleAccountNameChange(event) { accountName.value = event.target.value; diff --git a/examples/counter/lwc/countTracker/countTracker.html b/examples/counter/lwc/countTracker/countTracker.html index 84e32bb..44645ee 100644 --- a/examples/counter/lwc/countTracker/countTracker.html +++ b/examples/counter/lwc/countTracker/countTracker.html @@ -1,6 +1,4 @@ diff --git a/examples/counter/lwc/countTracker/countTracker.js b/examples/counter/lwc/countTracker/countTracker.js index 5a3cfa2..db10620 100644 --- a/examples/counter/lwc/countTracker/countTracker.js +++ b/examples/counter/lwc/countTracker/countTracker.js @@ -1,19 +1,11 @@ import { LightningElement } from "lwc"; -import { $computed, $reactTo } from "c/signals"; +import { $computed } from "c/signals"; import { counter, counterPlusTwo } from "c/demoSignals"; export default class CountTracker extends LightningElement { - get currentCount() { - return $reactTo(counter); - } - reactiveProperty = $computed(() => (this.reactiveProperty = counter.value)) .value; - get counterMultiplied() { - return $computed(() => counter.value * 2).value; - } - counterPlusTwo = $computed(() => (this.counterPlusTwo = counterPlusTwo.value)) .value; } diff --git a/force-app/lwc/signals/core.js b/force-app/lwc/signals/core.js index 0990876..7886a9a 100644 --- a/force-app/lwc/signals/core.js +++ b/force-app/lwc/signals/core.js @@ -1,111 +1,101 @@ const context = []; function _getCurrentObserver() { - return context[context.length - 1]; + return context[context.length - 1]; } function $effect(fn) { - const execute = () => { - context.push(execute); - try { - fn(); - } - finally { - context.pop(); - } - }; - execute(); + const execute = () => { + context.push(execute); + try { + fn(); + } finally { + context.pop(); + } + }; + execute(); } function $computed(fn) { - const computedSignal = $signal(fn()); - $effect(() => { - computedSignal.value = fn(); - }); - return computedSignal.readOnly; -} -function $reactTo(signal) { - let _value = signal.value; - $effect(() => { - _value = signal.value; - }); - return _value; + const computedSignal = $signal(fn()); + $effect(() => { + computedSignal.value = fn(); + }); + return computedSignal.readOnly; } function $signal(value) { - let _value = value; - const subscribers = new Set(); - function getter() { - const current = _getCurrentObserver(); - if (current) { - subscribers.add(current); - } - return _value; + let _value = value; + const subscribers = new Set(); + function getter() { + const current = _getCurrentObserver(); + if (current) { + subscribers.add(current); } - function setter(newValue) { - _value = newValue; - for (const subscriber of subscribers) { - subscriber(); - } + return _value; + } + function setter(newValue) { + _value = newValue; + for (const subscriber of subscribers) { + subscriber(); } - return { - get value() { - return getter(); - }, - set value(newValue) { - setter(newValue); - }, - readOnly: { - get value() { - return getter(); - } - } - }; + } + return { + get value() { + return getter(); + }, + set value(newValue) { + setter(newValue); + }, + readOnly: { + get value() { + return getter(); + } + } + }; } function $resource(fn, source, options) { - function loadingState(data) { - return { - data: data, - loading: true, - error: null - }; - } - let _isInitialLoad = true; - let _value = options?.initialValue ?? null; - let _previousParams; - const _signal = $signal(loadingState(_value)); - const execute = async () => { - _signal.value = loadingState(_value); - const derivedSource = source instanceof Function ? source() : source; - if (!_isInitialLoad && derivedSource === _previousParams) { - // No need to fetch the data again if the params haven't changed - return; - } - try { - const data = await fn(derivedSource); - // Keep track of the previous value - _value = data; - _signal.value = { - data, - loading: false, - error: null - }; - } - catch (error) { - _signal.value = { - data: null, - loading: false, - error - }; - } - finally { - _previousParams = derivedSource; - _isInitialLoad = false; - } - }; - $effect(execute); + function loadingState(data) { return { - data: _signal.readOnly, - refetch: async () => { - _isInitialLoad = true; - await execute(); - } + data: data, + loading: true, + error: null }; + } + let _isInitialLoad = true; + let _value = options?.initialValue ?? null; + let _previousParams; + const _signal = $signal(loadingState(_value)); + const execute = async () => { + _signal.value = loadingState(_value); + const derivedSource = source instanceof Function ? source() : source; + if (!_isInitialLoad && derivedSource === _previousParams) { + // No need to fetch the data again if the params haven't changed + return; + } + try { + const data = await fn(derivedSource); + // Keep track of the previous value + _value = data; + _signal.value = { + data, + loading: false, + error: null + }; + } catch (error) { + _signal.value = { + data: null, + loading: false, + error + }; + } finally { + _previousParams = derivedSource; + _isInitialLoad = false; + } + }; + $effect(execute); + return { + data: _signal.readOnly, + refetch: async () => { + _isInitialLoad = true; + await execute(); + } + }; } -export { $signal, $effect, $computed, $reactTo, $resource }; +export { $signal, $effect, $computed, $resource }; diff --git a/src/lwc/signals/core.ts b/src/lwc/signals/core.ts index a0aad26..1580766 100644 --- a/src/lwc/signals/core.ts +++ b/src/lwc/signals/core.ts @@ -14,7 +14,7 @@ function _getCurrentObserver(): VoidFunction | undefined { return context[context.length - 1]; } -function $effect(fn: VoidFunction): void { +function $effect(fn: VoidFunction): void {121 const execute = () => { context.push(execute); try { @@ -39,15 +39,6 @@ function $computed(fn: ComputedFunction): ReadOnlySignal { return computedSignal.readOnly; } -function $reactTo(signal: Signal): T { - let _value: T = signal.value; - $effect(() => { - _value = signal.value; - }); - - return _value; -} - function $signal(value: T): Signal { let _value: T = value; const subscribers: Set = new Set(); @@ -162,4 +153,4 @@ function $resource( }; } -export { $signal, $effect, $computed, $reactTo, $resource }; +export { $signal, $effect, $computed, $resource };