{"id":14990099,"url":"https://github.com/cesarparra/lwc-signals","last_synced_at":"2025-04-12T02:05:27.525Z","repository":{"id":240444919,"uuid":"801767286","full_name":"cesarParra/lwc-signals","owner":"cesarParra","description":"A simple yet powerful reactive store for Lightning Web Components based on Signals.","archived":false,"fork":false,"pushed_at":"2025-02-03T12:43:20.000Z","size":11370,"stargazers_count":10,"open_issues_count":6,"forks_count":4,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-12T02:05:17.002Z","etag":null,"topics":["lwc","lwc-component","salesforce","signals","state","state-management","web-components","web-components-library"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cesarParra.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-05-16T21:54:53.000Z","updated_at":"2025-02-14T13:37:21.000Z","dependencies_parsed_at":"2024-05-18T23:24:52.489Z","dependency_job_id":"4947d43c-6d17-469f-b4ef-28f85e177dc6","html_url":"https://github.com/cesarParra/lwc-signals","commit_stats":{"total_commits":103,"total_committers":1,"mean_commits":103.0,"dds":0.0,"last_synced_commit":"fdd6844af40e67f89e94be44984a7f7fa0d6cd71"},"previous_names":["cesarparra/lwc-signals"],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesarParra%2Flwc-signals","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesarParra%2Flwc-signals/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesarParra%2Flwc-signals/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesarParra%2Flwc-signals/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cesarParra","download_url":"https://codeload.github.com/cesarParra/lwc-signals/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248505863,"owners_count":21115354,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["lwc","lwc-component","salesforce","signals","state","state-management","web-components","web-components-library"],"created_at":"2024-09-24T14:19:27.373Z","updated_at":"2025-04-12T02:05:27.502Z","avatar_url":"https://github.com/cesarParra.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# LWC Signals\n\nA simple yet powerful reactive state management solution for Lightning Web Components.\n\n---\n\n![GitHub Workflow Status](https://github.com/cesarParra/lwc-signals/actions/workflows/ci.yml/badge.svg)\n![Version](https://img.shields.io/badge/version-1.5.0-blue)\n\nInspired by the Signals technology behind SolidJs, Preact, Svelte 5 Runes and the Vue 3 Composition API, LWC Signals is\na reactive signals implementation for Lightning Web Components.\nIt allows you to create reactive data signals that can be used to share up-to-date state between components.\n\nIt features:\n\n- 🚀 **Reactivity** Based on SolidJs and Preact Signals it provides a fine-grained reactivity system that will\n  automatically track dependencies and free them when not needed\n- 🦥 **Lazy Evaluation** Signals are lazy and will only compute values when read.\n- 🔬️ **Small Surface** The API does not offer more than what is needed, keeping the learning curve and bloat to a\n  minimum\n\nThe goal is to allow you to create beautiful and complex user experiences, while achieving clean code that\nseparates concerns and is easy to maintain!\n\nEasily implement:\n\n- Reactive data stores\n- Undo\n- Optimistic updates\n- Data caching through storage (localStorage, cookies, etc)\n\n\u003cp align=\"center\"\u003e\n    \u003cimg width=\"500\" src=\"./doc-assets/full-example.gif\" alt=\"Kitchen Sink Example\" /\u003e\n\u003c/p\u003e\n\n\u003e To see the code for the example above, check the `examples/shopping-cart` folder.\n\n# Getting Started\n\nCopy the `force-app/lwc/signals` folder to your project.\n\n\u003e ✏️ Note that the source code is written in Typescript and is located in the `src` folder. The `force-app/lwc/signals`\n\u003e folder contains the compiled code that you will be able to use in your Salesforce project. If you wish to modify the\n\u003e source\n\u003e code you can either modify the resulting JS code, or you can grab the Typescript files from the `src` folder\n\u003e and set up your project to compile them.\n\n## Examples\n\nTo see a live example, clone this repository, create a scratch org and push the code to it.\n\nThe org comes with a pre-built Experience Site that contains the examples.\n\nGo to:\n\n- Setup\n- All Sites\n- Locate the `examples` site\n- Click on `Builder`\n- Publish the site\n\nYou can then access the examples by going to the URL of the site.\n\n# Usage\n\n## Introduction\n\nThe LWC framework relies on message passing (either through pub/sub, CustomEvents or the Lightning Message Service (\nLMS))\nto communicate between components.\n\nThis can be cumbersome when you have a lot of components that need to share state for many reasons:\n\n- You have to dispatch and handle the state events through all the components in the hierarchy (when using CustomEvents)\n- You have to manage the state in a parent component, which relies on having the component on the page (when using\n  pub/sub and messages)\n- You have to make sure manage subscriptions and unsubscriptions to events\n\nAn alternative is to use the `wire` service to get the data from the server and let the framework handle the caching\nfor you, but this only works for data that is stored in the server, and still forces you to implement a lot of\nboilerplate code to manage each wire adapter for each component.\n\n`LWC Signals` provides a simple way to create reactive data signals that can be used to share state between components\nwithout the need to broadcast messages or manage subscriptions and wires.\n\n## Creating a signal\n\n\u003e 👀 You can find the full working code for the following example in the `examples`\n\u003e folder.\n\nA `signals` is simply an object with a `.value` property which holds a value. Any signal you create should be an LWC\nService that exports your signals.\n\n```javascript\n// LWC Service: counter-signals.js\nimport { $signal } from \"c/signals\";\n\nconst counter = $signal(0);\n\nexport { counter };\n```\n\n## Consuming the signal\n\nYou can use the signal in any LWC component by importing it and using the `.value` property.\n\nFor example, let's create a simple counter component that increments and decrements the counter when a button is\nclicked.\n\n```html\n\u003c!-- counter.html --\u003e\n\u003ctemplate\u003e\n  \u003cdiv\u003e\n    \u003cbutton onclick=\"{decrement}\"\u003eDecrement\u003c/button\u003e\n    \u003cbutton onclick=\"{increment}\"\u003eIncrement\u003c/button\u003e\n  \u003c/div\u003e\n\u003c/template\u003e\n```\n\nTo update the counter, you can change the `counter.value` property directly.\n\n```javascript\n// counter.js\nimport { LightningElement } from \"lwc\";\nimport { counter } from \"c/counter-signals\";\n\nexport default class Counter extends LightningElement {\n  increment() {\n    counter.value++;\n  }\n\n  decrement() {\n    counter.value--;\n  }\n}\n```\n\n## Reacting to changes\n\n### Through `$bind`\n\nYou can use the `$bind` function to create a reactive value that depends on the signal.\n\n```javascript\n// display.js\nimport { LightningElement } from \"lwc\";\nimport { $bind } from \"c/signals\";\nimport { counter } from \"c/counter-signals\";\n\nexport default class Display extends LightningElement {\n  counterProp = $bind(this, \"counterProp\").to(counter);\n}\n```\n\nNote that the first argument to the `$bind` function is the `this` context of the component, and the second argument\nis the name of the property that will be created on the component as a string. Then you call the `.to` function with\nthe signal you want to bind to.\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"./doc-assets/counter-example.gif\" alt=\"Counter Example\" /\u003e\n\u003c/p\u003e\n\n### Through `$computed`\n\nOne downside of using `$bind` is that the second argument is a string, which can lead to typos and errors if the\nproperty name is changed but the string is not updated.\n\nSo, alternatively, you can use the `$computed` function to create a reactive value that depends on the signal.\n\n```javascript\n// display.js\nimport { LightningElement } from \"lwc\";\nimport { $computed } from \"c/signals\";\nimport { counter } from \"c/counter-signals\";\n\nexport default class Display extends LightningElement {\n  counter = $computed(() =\u003e (this.counter = counter.value)).value;\n}\n```\n\nBut notice that this syntax is a lot more verbose than using `$bind`.\n\n\u003e ❗ Note that in the callback function we **need** to reassign the value to `this.counter`\n\u003e to trigger the reactivity. This is because we need the value to be reassigned so that\n\u003e LWC reactive system can detect the change and update the UI.\n\n### Through `$effect`\n\nFinally, you can use the `$effect` function to create a side effect that depends on the signal.\n\n```javascript\n// display.js\nimport { LightningElement } from \"lwc\";\nimport { $effect } from \"c/signals\";\nimport { counter } from \"c/counter-signals\";\n\nexport default class Display extends LightningElement {\n  counter = 0;\n\n  constructor() {\n    $effect(() =\u003e (this.counter = counter.value));\n  }\n}\n```\n\n#### Stacking computed values\n\nYou can also stack computed values to create more complex reactive values that derive from each other\n\n```javascript\nimport { $signal, $computed } from \"c/signals\";\n\nexport const counter = $signal(0);\n\nexport const counterPlusOne = $computed(() =\u003e counter.value + 1);\nexport const counterPlusTwo = $computed(() =\u003e counterPlusOne.value + 1);\n```\n\nBecause `$computed` values return a signal, you can use them as you would use any other signal.\n\n### `$effect`\n\nYou can also use the `$effect` function to create a side effect that depends on a signal.\n\nLet's say you want to keep a log of the changes in the `counter` signal.\n\n```javascript\nimport { $signal, $effect } from \"c/signals\";\n\nexport const counter = $signal(0);\n\n$effect(() =\u003e console.log(counter.value));\n```\n\n\u003e ❗ DO NOT use `$effect` to update the signal value, as it will create an infinite loop.\n\n## Peeking at the signal value\n\nIf the rare case that you have an effect that needs to read of a signal without subscribing to it, you can\nuse the signal's `peek` function to read the value.\n\n```javascript\nimport { $signal, $effect } from \"c/signals\";\n\nconst counter = $signal(0);\n\n$effect(() =\u003e console.log(counter.peek()));\n```\n\nThis can be useful when you need to update the value of a signal based on its current value, but you want\nto avoid causing a circular dependency.\n\n```javascript\nconst counter = $signal(0);\n$effect(() =\u003e {\n  // Without peeking, this kind of operation would cause a circular dependency.\n  counter.value = counter.peek() + 1;\n});\n```\n\nNote that you should use this feature sparingly, as it can lead to bugs that are hard to track down.\nThe preferred way of reading a signal is through the `signal.value`.\n\n## Error Handling\n\nWhen unhandled errors occur in a `computed`, `effect` or `resource`,\nby default, the error will be logged to the console through a `console.error` call, and then the error will be rethrown.\n\nIf you wish to know where the error is coming from, you can pass a second argument to the `computed`,\n`effect` or `resource` with a unique identifier.\n\n```javascript\n$computed(\n  () =\u003e {\n    signal.value;\n    throw new Error(\"error\");\n  },\n  { identifier: \"test-identifier\" }\n);\n\n$effect(\n  () =\u003e {\n    signal.value;\n    throw new Error(\"error\");\n  },\n  { identifier: \"test-identifier\" }\n);\n\n$resource(\n  asyncFunction,\n  {},\n  {\n    identifier: \"test-identifier\"\n  }\n);\n```\n\nThis value will be used only for debugging purposes, and does not affect the functionality otherwise.\n\nIn this example, the test-identifier string will appear as part of the console.error message.\n\n### Custom Error Handlers\n\n`computed`, `effect`, and `resource` signals can all receive a custom `onError` property,\nthat allows developers to completely override the default functionality that logs and rethrows the error.\n\n#### Effect handlers\n\nFor `$effect` handlers, you can pass a function with the following shape:\n\n```typescript\n(error: any, options: { identifier: string | symbol }) =\u003e void\n```\n\nThe function will receive the thrown error as the first argument, and an object with the identifier as the second.\nIt should not return anything.\n\nExample:\n\n```javascript\nfunction customErrorHandlerFn(error) {\n  // custom logic or logging or rethrowing here\n}\n\n$effect(\n  () =\u003e {\n    throw new Error(\"test\");\n  },\n  {\n    onError: customErrorHandlerFn\n  }\n);\n```\n\n#### Computed handlers\n\nFor `$computed` handlers, you can pass a function with the following shape:\n\n```typescript\n(error: unknown, previousValue: T, options: { identifier: string | symbol }) =\u003e\n  T | undefined;\n```\n\nWhere you can return nothing, or a value of type `T`, which should be of the same type as the computed value itself.\nThis allows you to provide a \"fallback\" value, that the computed value will receive in case of errors.\n\nAs a second argument, you will receive the previous value of the computed signal, which can be useful to provide a\nfallback value based on the previous value.\n\nThe third argument is an object with the received identifier.\n\nExample\n\n```javascript\nfunction customErrorHandlerFn(error, _previousValue, _options) {\n  // custom logic or logging or rethrowing here\n  return \"fallback value\";\n}\n\n$computed(\n  () =\u003e {\n    throw new Error(\"test\");\n  },\n  {\n    onError: customErrorHandlerFn\n  }\n);\n```\n\n#### Resource handlers\n\nFor `resource` handlers, you can pass a function with the following shape:\n\n```typescript\n(error: unknown, previousValue: T | null, options: { initialValue: T | null, identifier: string | symbol }) =\u003e\n        AsyncData\u003cT\u003e | void\n\n// Where AsyncData looks as follows\n// {\n//   data: T | null;\n//   loading: boolean;\n//   error: unknown | null;\n// };\n```\n\nWhere you can return nothing, or a value of type `AsyncData\u003cT\u003e`.\n`AsyncData` is the shape that all resources take, and it contains the data, loading state, and error state.\nThis allows you to provide a \"fallback\" value, that the computed value will receive in case of errors.\n\nAs a second argument, you will receive the previous value of the resource (or null if there is none), which can be useful to provide a\nfallback value based on the previous value.\n\nThe third argument is an object with the received identifier as well as any initial value that was provided to the\nresource when it was created.\n\nExample\n\n```javascript\nfunction customErrorHandlerFn(error, _previousValue, _options) {\n  // custom logic or logging or rethrowing here\n  return {\n    data: \"fallback value\",\n    loading: false,\n    error: error\n  };\n}\n\n$resource(\n  asyncFunction,\n  {},\n  {\n    onError: customErrorHandlerFn\n  }\n);\n```\n\n## Tracking objects and arrays\n\nBy default, for a signal to be reactive it needs to be reassigned. This can be cumbersome when dealing with objects\nand arrays, as you would need to reassign the whole object or array to trigger the reactivity.\n\nTo improve that experience, you can set the `track` flag to true when creating the signal. This will make the signal\nreactive to changes in the object or array properties.\n\n\u003e 📒 Think about this as using the `@track` decorator in LWC properties. It works the exact same way behind the scenes.\n\n```javascript\nimport { $signal } from \"c/signals\";\n\nconst obj = $signal({ x: 1, y: 2 }, { track: true });\nconst computedFromObj = $computed(() =\u003e obj.value.x + obj.value.y);\n\n// When a value in the object changes, the computed value will automatically update\nobj.value.x = 2;\n\nconsole.log(computedFromObj.value); // 4\n```\n\n## Reacting to multiple signals\n\nYou can also use multiple signals in a single `computed` or `effect` and react to changes in any of them.\nThis allows you to create complex reactive values that depend on multiple data sources\nwithout having to track each one independently.\n\n\u003e 👀 You can find the full working code for the following example in the `examples`\n\u003e folder.\n\n**Given the following signals**\n\n```javascript\n// LWC Service: contact-info.js\n\nimport { $signal } from \"c/signals\";\n\nexport const accountName = $signal(\"ACME\");\n\nexport const contactName = $signal(\"John Doe\");\n```\n\n**And given a component that updates both signals**\n\n```html\n\u003c!-- contactInfoForm.html --\u003e\n\u003ctemplate\u003e\n  \u003clightning-input\n    label=\"Account Name\"\n    value=\"{accountName}\"\n    onchange=\"{handleAccountNameChange}\"\n  \u003e\u003c/lightning-input\u003e\n  \u003clightning-input\n    label=\"Contact Name\"\n    value=\"{contactName}\"\n    onchange=\"{handleContactNameChange}\"\n  \u003e\u003c/lightning-input\u003e\n\u003c/template\u003e\n```\n\n```javascript\n// contactInfoForm.js\nimport { LightningElement } from \"lwc\";\nimport { $computed } from \"c/signals\";\nimport { accountName, contactName } from \"c/demoSignalss\";\n\nexport default class ContactInfoForm extends LightningElement {\n  accountName = $computed(() =\u003e (this.accountName = accountName.value)).value;\n\n  contactName = $computed(() =\u003e (this.contactName = contactName.value)).value;\n\n  handleAccountNameChange(event) {\n    accountName.value = event.target.value;\n  }\n\n  handleContactNameChange(event) {\n    contactName.value = event.target.value;\n  }\n}\n```\n\n**You can create a computed value that depends on both signals**\n\n```html\n\u003c!-- businessCard.html --\u003e\n\u003ctemplate\u003e\n  \u003cdiv class=\"slds-card\"\u003e\n    \u003cdiv class=\"slds-card__body slds-card__body_inner\"\u003e\n      \u003cdiv\u003eAccount Name: {contactInfo.accountName}\u003c/div\u003e\n      \u003cdiv\u003eContact Name: {contactInfo.contactName}\u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\u003c/template\u003e\n```\n\n```javascript\n// businessCard.js\nimport { LightningElement } from \"lwc\";\nimport { $computed } from \"c/signals\";\nimport { accountName, contactName } from \"c/demoSignals\";\n\nexport default class BusinessCard extends LightningElement {\n  contactInfo = $computed(\n    () =\u003e\n      (this.contactInfo = {\n        accountName: accountName.value,\n        contactName: contactName.value\n      })\n  ).value;\n}\n```\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"./doc-assets/business-card-example.gif\" alt=\"Counter Example\" /\u003e\n\u003c/p\u003e\n\n\u003e ❗ Notice that we are using a property instead of a getter in the `$computed` callback function, because\n\u003e we need to reassign the value to `this.contactInfo` to trigger the reactivity, as it is a complex object.\n\n## Communicating with Apex data and other asynchronous operations\n\nYou can also use the signals framework to communicate with Apex data and other asynchronous operations.\n\nIn a traditional LWC component, you would use the `@wire` service to fetch data from the server and update the UI,\nor you could declaratively call Apex methods by importing them and calling them directly.\n\nIf you only wish to fetch data once and hold that data within a component, you should still use the `@wire` service\nor imperative Apex calls within your own component.\n\nWhere the Signals framework comes in handy is when you wish for multiple components to share the same data to have a\nsingle\nsource of truth, and/or when you want to have a reactive system that updates the UI automatically when the data changes,\nno matter where the change comes from.\n\n### $resource\n\nThe `$resource` function is a helper function that allows you to create a signal that fetches data asynchronously,\nwhich includes Apex methods imported through `@salesforce/apex/`.\n\n---\n\nLet's first take a look at the simple example of fetching data from the server through a single source of truth\n(the resource signal) and sharing it between components.\n\n**Given the following Apex method**\n\n```apex\n// Apex Class: ContactController.cls\npublic with sharing class ContactController {\n    @AuraEnabled(cacheable=true)\n    public static List\u003cContact\u003e getContacts() {\n        return [SELECT Id, Name FROM Contact];\n    }\n}\n```\n\n**And the following signal**\n\n```javascript\n// LWC Service: contact-signals.js\nimport { $resource } from \"c/signals\";\nimport getContacts from \"@salesforce/apex/ContactController.getContacts\";\n\n// Notice that we have to destructure the data property from the resource signal\n// We explain why below in the \"refetching\" section :)\nexport const { data: fetchContacts } = $resource(getContacts);\n```\n\n**You can use the signal in any component**\n\n```html\n\u003c!-- contactList.html --\u003e\n\u003ctemplate\u003e\n  \u003ctemplate if:true=\"{contacts.loading}\"\u003e Loading\u003c/template\u003e\n  \u003ctemplate if:false=\"{contacts.loading}\"\u003e\n    \u003ctemplate for:each=\"{contacts.data}\" for:item=\"contact\"\u003e\n      \u003cdiv key=\"{contact.Id}\"\u003e\n        \u003cp\u003e{contact.Name}\u003c/p\u003e\n      \u003c/div\u003e\n    \u003c/template\u003e\n  \u003c/template\u003e\n\u003c/template\u003e\n```\n\n```javascript\n// contactList.js\nimport { LightningElement } from \"lwc\";\nimport { $computed } from \"c/signals\";\nimport { fetchContacts } from \"c/contact-signals\";\n\nexport default class ContactList extends LightningElement {\n  contacts = $computed(() =\u003e (this.contacts = fetchContacts.value)).value;\n}\n```\n\nData from a resource signal comes in as a read-only signal in the following format:\n\n```typescript\ntype AsyncData\u003cT\u003e = {\n  data: T | null; // The data fetched from the server. It is null until the data is fetched\n  loading: boolean; // A boolean that indicates if the data is being fetched\n  error: unknown | null; // An error object that is populated if the fetch fails\n};\n```\n\n\u003e 🍪 One benefit of using the `$resource` over declarative Apex or `@wire` is that it keeps track of the loading\n\u003e state for you, which saves you the effort of having to calculate it yourself.\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"./doc-assets/apex-fetch.gif\" alt=\"Fetching From Apex\" /\u003e\n\u003c/p\u003e\n\n---\n\nLet's now take a look at the more complex example of fetching data from the server through a method that takes\nparameters, and having it react once those parameters change.\n\nImagine we have 2 components, one that displays a list of accounts and another that displays the details of a single\nselected account. We want the details component to update whenever the selected account changes. Let's see how we can\nachieve this with.\n\n**Given the following Apex controller**\n\n```apex\npublic with sharing class ResourceController {\n    @AuraEnabled(Cacheable=true)\n    public static List\u003cAccount\u003e getAccounts() {\n        return [SELECT Id, Name FROM Account];\n    }\n\n    @AuraEnabled\n    public static Account getAccountDetails(Id accountId) {\n        if (accountId == null) {\n            return null;\n        }\n        return [SELECT Id, Name, Phone, Website FROM Account WHERE Id = :accountId];\n    }\n}\n```\n\nWe can have a signal that keeps track of which Account Id has been selected, and a `resource` signal that fetches the\ndetails of the selected account.\n\n```javascript\nimport { $signal, $resource, $effect } from \"c/signals\";\nimport getAccountDetails from \"@salesforce/apex/ResourceController.getAccountDetails\";\n\nexport const selectedAccountId = $signal(null);\n\nexport const { data: getAccount } = $resource(getAccountDetails, () =\u003e ({\n  accountId: selectedAccountId.value\n}));\n```\n\nNotice that the resource signal takes a second optional argument, which in this case is a function that returns an\nobject with the parameters that the Apex method needs. Because this function is accessing a reactive\nvalue (`selectedAccountId`),\nthe resource signal will automatically refetch the data whenever the `selectedAccountId` changes!\n\n\u003e 🍪 The value doesn't need to be a function unless you need the reactivity, it can also be a regular JS object in the\n\u003e format expected by your Apex method (e.g. `{ accountId: \"001200000XyZ1QAQ\" }`).\n\nThis works no matter how many reactive values you use in the function, and it will automatically refetch the data\nwhenever any of the reactive values change.\n\nLet's now create our picklist component that allows the user to select an account.\n\n```html\n\u003c!-- accountPicker.html --\u003e\n\u003ctemplate\u003e\n  \u003clightning-select\n    label=\"Select Account\"\n    value=\"{currentAccountId}\"\n    options=\"{accounts}\"\n    onchange=\"{handleAccountChange}\"\n  \u003e\u003c/lightning-select\u003e\n\u003c/template\u003e\n```\n\n```javascript\n// accountPicker.js\nimport { LightningElement, track, wire } from \"lwc\";\nimport getAccounts from \"@salesforce/apex/ResourceController.getAccounts\";\nimport { selectedAccountId } from \"c/demoSignals\";\n\nexport default class AccountPicker extends LightningElement {\n  @track accounts = [];\n\n  @wire(getAccounts) getAccounts({ error, data }) {\n    if (data) {\n      this.accounts = data.map((account) =\u003e ({\n        label: account.Name,\n        value: account.Id\n      }));\n\n      if (this.accounts.length \u003e 0) {\n        selectedAccountId.value = this.accounts[0].value;\n      }\n    } else if (error) {\n      console.error(error);\n    }\n  }\n\n  get currentAccountId() {\n    return selectedAccountId.value;\n  }\n\n  handleAccountChange(event) {\n    selectedAccountId.value = event.detail.value;\n  }\n}\n```\n\nNotice how we are using a `@wire` service to fetch the accounts from the server and populate the picklist. This is\nbecause in this case we don't care about sharing that data with other components, and we only need it once. Be\npragmatic about when to use signals and when not to. Opt to use the base Salesforce services when you only need the\ndata\nin a single component.\n\nNow, let's create the component that displays the details of the selected account.\n\n```html\n\u003c!-- accountDetails.html --\u003e\n\u003ctemplate\u003e\n  \u003cdiv\u003e\n    \u003ch1\u003eSelected Account\u003c/h1\u003e\n    \u003ctemplate if:true=\"{account.loading}\"\u003e\n      \u003clightning-spinner\n        alternative-text=\"Loading\"\n        size=\"large\"\n      \u003e\u003c/lightning-spinner\u003e\n    \u003c/template\u003e\n\n    \u003ctemplate if:false=\"{account.loading}\"\u003e\n      \u003ctemplate if:true=\"{account.data}\"\u003e\n        \u003cp\u003eAccount Name: {account.data.Name}\u003c/p\u003e\n        \u003cp\u003ePhone: {account.data.Phone}\u003c/p\u003e\n        \u003cp\u003eWebsite: {account.data.Website}\u003c/p\u003e\n      \u003c/template\u003e\n    \u003c/template\u003e\n  \u003c/div\u003e\n\u003c/template\u003e\n```\n\n```javascript\n// accountDetails.js\nimport { LightningElement } from \"lwc\";\nimport { $computed } from \"c/signals\";\nimport { getAccount } from \"c/demoSignals\";\n\nexport default class AccountDetails extends LightningElement {\n  account = $computed(() =\u003e (this.account = getAccount.value)).value;\n}\n```\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"./doc-assets/account-picker.gif\" alt=\"Account Picker Example\" /\u003e \n\u003c/p\u003e\n\n\u003e 🍪 One extra feature of the data returned by the `$resource` function is that when it is reloading the data, the\n\u003e previous data is still available in the `data` property. This allows you to keep the old value while the new value is\n\u003e being loaded and provide for a smoother experience, to avoid flickering or loading spinners that disappear\n\u003e immediately,\n\u003e when you know the data is going to be fetched quickly.\n\n---\n\n### Providing a default value to an async resource\n\nYou can provide a default value to an async resource by passing it as the third argument to the `$resource` function.\n\n```javascript\nimport { $resource } from \"c/signals\";\n\nconst { data: resource } = $resource(asyncFunction, undefined, {\n  initialValue: \"initial\"\n});\n```\n\n### Refetching data\n\nWhen you use the `$resource` function, the signal will automatically refetch the data whenever the reactive values\nchange. This is useful when you want to refetch the data when the parameters change, but it can also be a problem when\nyou want to keep the data in the signal and only refetch it when you explicitly tell it to.\n\nTo solve this problem, you can use the `refetch` function that is returned by the `$resource` function.\n\n```javascript\nimport { $signal, $resource } from \"c/signals\";\nimport getContacts from \"@salesforce/apex/ContactController.getContacts\";\n\nexport const { data: fetchContacts, refetch: refetchContacts } =\n  $resource(getContacts);\n```\n\nYou can then call the `refetch` function whenever you want to refetch the data.\n\n```javascript\n// contactList.js\nimport { LightningElement } from \"lwc\";\nimport { $computed } from \"c/signals\";\nimport { fetchContacts, refetchContacts } from \"c/contact-signals\";\n\nexport default class ContactList extends LightningElement {\n  contacts = $computed(() =\u003e (this.contacts = fetchContacts.value)).value;\n\n  handleRefresh() {\n    refetchContacts();\n  }\n}\n```\n\n### Mutating `$resource` data\n\nNotice that the data returned by a resource is a ReadOnlySignal.\nThis means that you cannot mutate the data directly, so how can you update the data?\n\nBesides `refetch`, the `$resource` function also returns a `mutate` function that allows you to mutate the data.\n\n`mutate` is useful when you want to update the data without refetching it (and avoid a trip to the server).\n\nIt receives a single value, which will be set as the new value of the data. The `resource` value will be updated\nimmediately, the `.loading` property will be set to `false`, and the `.error` property will be set to `null`.\n\n```javascript\nimport { $resource } from \"c/signals\";\n\nconst { data, mutate } = $resource(asyncFunction);\n\nmutate(\"new value\");\n```\n\n#### Reacting to mutated values\n\nWhen using the `mutate` function, you might want to react to the changes in the data. For example, you might now\nwant to call an Apex function to save the new value to the server, and make sure the data is synced.\n\nFor this, you can provide a function through the options object's `onMutate`.\n\nThe function you provide can receive 3 arguments:\n\n- The new value\n- The old value\n- A `mutate` function that you can use the update the data again. This can be used for when you want to update the data\n  based on what was returned from the server, but you don't want to refetch the data. You SHOULD use this mutate\n  function over the one returned when creating the `$resource` because this will not trigger `onMutate` once again.\n\n```javascript\nimport { $resource } from \"c/signals\";\nimport getContacts from \"@salesforce/apex/ContactController.getContacts\";\nimport saveContacts from \"@salesforce/apex/ContactController.saveContacts\";\n\nconst { data, mutate } = $resource(\n  getContacts,\n  {},\n  {\n    onMutate: async (newValue, oldValue, mutate) =\u003e {\n      await saveContacts({ contacts: newValue });\n      mutate(newValue);\n    }\n  }\n);\n```\n\nIn the case an error occurs on your server call, the `mutate` you can pass an error object as the second argument to\nthe `mutate` function. This will set the `.error` property of the `resource` to the error object.\n\n```javascript\nimport { $resource } from \"c/signals\";\n\nconst { data, mutate } = $resource(asyncFunction);\n\ntry {\n  await saveContacts({ contacts: newValue });\n  mutate(newValue);\n} catch (error) {\n  mutate(null, error);\n}\n```\n\n#### Optimistic updating\n\nWhen you mutate a `resource` as exemplified above, you can achieve the concept of optimistic updating. This is when you\nupdate the value immediately before the server responds, and then update the value again when the server responds.\n\nOptimistically updating the value can provide a better user experience by making the UI feel more responsive, but it\ncan also lead to inconsistencies if the server responds with an error. So if you wish to turn this off, and\nmanage updating the value yourself, either by `refetching` or by using an `onMutate` function, you can set the\n`optimisticMutate` option to `false`.\n\n```javascript\nimport { $resource } from \"c/signals\";\n\nconst { data, refetch, mutate } = $resource(\n  asyncFunction,\n  {},\n  {\n    optimisticMutate: false\n  }\n);\n```\n\n#### Defining when to fetch and refetch data\n\nThere are situations when you don't want to fetch the data immediately when the component is created, or when you\ndon't want to refetch the data when the reactive values change.\n\nFor example, you might have an initial null value for a reactive value, and you only want to fetch the data when the\nvalue is set to a non-null value. In this situation you don't want to waste a call to the server since you know the\ndata is not going to be used or will come back as null.\n\nAn additional option you can pass to the `$resource` function is `fetchWhen`. This is any function that returns\na boolean. If the function returns `true`, the data will be fetched. If it returns `false`, the data will not be\nfetched.\n\n```javascript\nimport { $signal, $resource, $effect } from \"c/signals\";\nimport getAccountDetails from \"@salesforce/apex/ResourceController.getAccountDetails\";\n\nexport const selectedAccountId = $signal(null);\n\nexport const { data: getAccount } = $resource(\n  getAccountDetails,\n  () =\u003e ({\n    accountId: selectedAccountId.value\n  }),\n  {\n    fetchWhen: () =\u003e selectedAccountId.value !== null\n  }\n);\n```\n\n## Storage\n\nBy default, any created signal is stored in memory and will be lost when the component is destroyed. This behavior\ncan be modified by passing a `storage` option to the `$signal` function. For example, if you wish for\nthe signal to be stored in the `localStorage`, you can use `useLocalStorage` helper function.\n\n```javascript\nimport { $signal, useLocalStorage } from \"c/signals\";\n\nconst counter = $signal(0, { storage: useLocalStorage(\"my-key-name\") });\n```\n\nThe following storage helpers are available by default:\n\n- `useLocalStorage(key: string)`: Stores the signal in the `localStorage` with the given key\n- `useSessionStorage(key: string)`: Stores the signal in the `sessionStorage` with the given key\n- `useCookies(key: string, expires?: Date)`: Stores the signal in a cookie with the given key. You can also pass an\n  optional `expires` parameter to set the expiration date of the cookie\n- `useEventListener(eventName: string)`: Dispatches a CustomEvent to the `window` object with the given event name\n  whenever the signal changes. It also listens for events with the given name and updates the signal when the event is\n  received. This is useful for when you want to communicate changes to components that for some reason don't\n  have access to the signal (for example, a component that cannot import the signal because it lives in a different\n  namespace).\n\n  The event sent and expected to be received has the following format:\n\n  ```\n   {\n     detail: {\n       data: T;\n   };\n  ```\n\n  Where `T` is the type of the signal.\n\n  ```javascript\n  import { $signal, useEventListener } from \"c/signals\";\n\n  const counter = $signal(0, { storage: useEventListener(\"counter-change\") });\n  ```\n\n  and from another component\n\n  ```javascript\n  ...\n  connectedCallback() {\n    window.addEventListener(\"counter-change\", (event) =\u003e {\n    console.log(event.detail.data);\n    });\n  }\n  ...\n  handleSomeChange() {\n    window.dispatchEvent(new CustomEvent(\"counter-change\", { detail: { data: 1 } }));\n  }\n  ```\n\n- `useEventBus(channel: string, toValue: (response?: object) =\u003e T, options: object)`: Subscribes to the event bus\n  channel (e.g. platform event, change data capture, etc.).\n\n  - The `channel` parameter is the event bus channel to subscribe to.\n  - The `toValue` function is used to convert the response from the event bus to the desired value.\n\n  ```javascript\n  import { $signal, useEventBus } from \"c/signals\";\n  export const receivedEvent = $signal(undefined, {\n    storage: useEventBus(\"/event/PlatEvent__e\", ({ data }) =\u003e ({\n      message: data.payload.Message__c,\n      sender: data.payload.Sender__c,\n      time: data.payload.Time__c\n    }))\n  });\n  ```\n\n  The passed in argument will be the message received from the event bus, which\n  is of the following shape:\n\n  ```javascript\n  {\n      channel: string;\n      data: {\n        event: {\n          replayId: number;\n        },\n        payload: object,\n      };\n    }\n  ```\n\n  The `payload` key will contain the actual data of the event. For example,\n  if using a platform event, this will contain the fields of the platform event.\n\n  - The `options` (optional) parameter is an object that can contain the following properties (all of them optional):\n    - `replayId` The replay ID to start from, defaults to -1. When -2 is passed, it will replay from the last saved\n      event.\n    - `onSubscribe` A callback function called when the subscription is successful.\n    - `onError` A callback function called when an error response is received from the server for\n      handshake, connect, subscribe, and unsubscribe meta channels.\n\n  **Unsubscribing from the event bus**\n\n  When using the `useEventBus` storage, the signal will hold a special function called `unsubscribe` that you can call\n  to unsubscribe from the event bus.\n\n  ```javascript\n  import { $signal, useEventBus } from \"c/signals\";\n\n  const receivedEvent = $signal(undefined, {\n    storage: useEventBus(\"/event/PlatEvent__e\", ({ data }) =\u003e ({\n      message: data.payload.Message__c,\n      sender: data.payload.Sender__c,\n      time: data.payload.Time__c\n    }))\n  });\n\n  // Unsubscribe from the event bus\n  receivedEvent.unsubscribe();\n  ```\n\n### Creating a custom storage\n\nThe `storage` option receives a function that defines the behavior for where the data should be stored.\nThis means you can create your own custom storage solution by passing a function with the following\nsignature:\n\n```typescript\n// Note that we are using a Typescript signature for clarity,\n// but you can use the same signature in Javascript. The example below is in Javascript.\ntype StorageFn\u003cT\u003e = (value: T) =\u003e {\n  get: () =\u003e T;\n  set: (newValue: T) =\u003e void;\n};\n```\n\nTo make things easier, we provide a helper function that creates a storage solution for you: `createStorage`.\n\nFor example, let's say you want to create a storage solution that allows you to undo to the previous value of a signal\nas many times as you want.\n\n```javascript\nimport { $signal, createStorage } from \"c/signals\";\n\nconst useUndo = (value) =\u003e {\n  const _valueStack = [];\n\n  // add the initial value to the stack\n  _valueStack.push(value);\n\n  function undo() {\n    _valueStack.pop();\n  }\n\n  const customStorage = createStorage(\n    () =\u003e {\n      // Get value at the top of the stack\n      return _valueStack[_valueStack.length - 1];\n    },\n    (newValue) =\u003e {\n      _valueStack.push(newValue);\n    }\n  );\n\n  return {\n    ...customStorage,\n    undo\n  };\n};\n\nconst counter = $signal(0, { storage: useUndo });\n\ncounter.value = 1;\ncounter.value = 2;\ncounter.value = 3;\ncounter.undo(); // counter.value is now 2\ncounter.undo(); // counter.value is now 1\ncounter.undo(); // counter.value is now 0\n```\n\n`createStorage` receives two functions: a getter and a setter. The getter should return the value stored in the\nstorage, and the setter should set the value in the storage.\n\nNotice that any additional properties you add to the object returned by `createStorage` will be available in the\nreturned object. That is how we can add the `undo` function to the `counter` signal and use it to undo the changes.\n\n## Debouncing\n\nYou can debounce the changes to a signal by passing a `debounce` option to the `$signal` function. This\nis specially helpful when you have a `$resource` that re-fetches based on a reactive value that changes frequently.\n\n```javascript\nimport { $signal } from \"c/signals\";\n\nconst counter = $signal(0, { debounce: 500 });\n```\n\nWhen you debounce a signal, the value will only be updated after the specified time has passed since the last change.\n\n## Examples\n\nYou can find full working examples in the `examples` folder.\n\nFor a full kitchen sink example that combines all the concepts, you can check the `shopping-cart` example.\n\nIt includes:\n\n- Getting data from the server\n- Optimistic updates by updating the local value on change\n- Re-updating the value when the server responds\n- Undo functionality by storing the state history in a custom signal\n- Caching the data in the `localStorage` for a fast first load.\n\n\u003cp align=\"center\"\u003e\n    \u003cimg width=\"500\" src=\"./doc-assets/kitchen-sink.gif\" alt=\"Kitchen Sink Example\" /\u003e\n\u003c/p\u003e\n\n# Contributing\n\nContributions are welcome! Please read the [Contributing Guide](CONTRIBUTING.md) for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcesarparra%2Flwc-signals","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcesarparra%2Flwc-signals","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcesarparra%2Flwc-signals/lists"}