{"id":25028831,"url":"https://github.com/leandrobrunner/lwc-signals","last_synced_at":"2026-02-08T12:18:16.483Z","repository":{"id":275363659,"uuid":"925284925","full_name":"leandrobrunner/lwc-signals","owner":"leandrobrunner","description":"A lightweight reactive state management library for Salesforce Lightning Web Components.","archived":false,"fork":false,"pushed_at":"2025-10-05T09:31:18.000Z","size":108,"stargazers_count":14,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-05T11:30:42.669Z","etag":null,"topics":["lightning-web-components","lwc","lwc-component","lwc-opensource","salesforce","salesforce-developers","signals"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/leandrobrunner.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":null,"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"lfx_crowdfunding":null,"polar":null,"buy_me_a_coffee":null,"thanks_dev":null,"custom":null}},"created_at":"2025-01-31T15:33:46.000Z","updated_at":"2025-10-01T07:36:47.000Z","dependencies_parsed_at":null,"dependency_job_id":"ec883741-7cc5-4619-a244-fd0bb88670e1","html_url":"https://github.com/leandrobrunner/lwc-signals","commit_stats":null,"previous_names":["leandrobrunner/lwc-signals"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/leandrobrunner/lwc-signals","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leandrobrunner%2Flwc-signals","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leandrobrunner%2Flwc-signals/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leandrobrunner%2Flwc-signals/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leandrobrunner%2Flwc-signals/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/leandrobrunner","download_url":"https://codeload.github.com/leandrobrunner/lwc-signals/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leandrobrunner%2Flwc-signals/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29229789,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-08T12:03:03.049Z","status":"ssl_error","status_checked_at":"2026-02-08T12:02:56.077Z","response_time":57,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["lightning-web-components","lwc","lwc-component","lwc-opensource","salesforce","salesforce-developers","signals"],"created_at":"2025-02-05T20:47:53.026Z","updated_at":"2026-02-08T12:18:16.471Z","avatar_url":"https://github.com/leandrobrunner.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# LWC Signals\n\nA lightweight reactive state management library for Salesforce Lightning Web Components.\n\n\u003ca href=\"https://githubsfdeploy.herokuapp.com?owner=leandrobrunner\u0026repo=lwc-signals\u0026ref=main\"\u003e\n  \u003cimg alt=\"Deploy to Salesforce\"\n       src=\"https://raw.githubusercontent.com/afawcett/githubsfdeploy/master/deploy.png\"\u003e\n\u003c/a\u003e\n\n## Features\n\n- 🚀 Fine-grained reactivity\n- 📦 Zero dependencies\n- 🔄 Deep reactivity for objects and collections\n- 📊 Computed values with smart caching\n- 🎭 Batch updates for performance\n- ⚡ Small and efficient\n\n## About This Library\n\nThis library brings the power of signals to Salesforce Lightning Web Components today. While Salesforce has [conceptualized signals](https://www.npmjs.com/package/@lwc/signals) as a future feature for LWC, it's currently just a concept and not available for use. \n\nThis library provides:\n- Complete signals implementation\n- Rich feature set beyond basic signals:\n  - Computed values\n  - Effects\n  - Batch updates\n  - Deep reactivity\n  - Manual subscriptions\n- Design aligned with Salesforce's signals concept for future compatibility\n\nInspired by:\n- [Preact Signals](https://preactjs.com/guide/v10/signals/) - Fine-grained reactivity system\n- Salesforce's signals concept and API design principles\n\n## Unlocked Package\n\nProduction / Dev:\n```\nhttps://login.salesforce.com/packaging/installPackage.apexp?p0=04tbm000000LkUTAA0\n```\n\nSandbox / Scratch:\n```\nhttps://test.salesforce.com/packaging/installPackage.apexp?p0=04tbm000000LkUTAA0\n```\n\nYou can also install using the SF CLI:\n```bash\nsf package install --package \"04tbm000000LkUTAA0\"\n```\n\n## Installation from [NPM](https://www.npmjs.com/package/lwc-signals)\n\n### Step 1: Install the Package\n\nIn your project folder, run:\n\n```\nnpm install --save lwc-signals\n```\n\n### Step 2: Link the Component to Your Salesforce Project\n\nAfter installation, link the LWC component from `node_modules` into your Salesforce project so it’s available as a standard Lightning Web Component.\n\n#### On macOS / Linux\n\nRun:\n\n```\nln -s ../../../../node_modules/lwc-signals/dist/signals ./force-app/main/default/lwc/signals\n```\n\n#### On Windows\n\nOption A: Using Command Prompt (run as Administrator)\n\n```\nmklink /D \"force-app\\main\\default\\lwc\\signals\" \"..\\..\\..\\..\\node_modules\\lwc-signals\\dist\\signals\"\n```\n\nOption B: Using PowerShell\n\n```\nNew-Item -ItemType SymbolicLink -Path \"force-app\\main\\default\\lwc\\signals\" -Target \"..\\..\\..\\..\\node_modules\\lwc-signals\\dist\\signals\"\n```\n\nNote: If you are not running as Administrator, enable Developer Mode on Windows to allow symlink creation.\n\n\n## Core Concepts\n\n### Signals\n```javascript\nconst name = signal('John');\nconsole.log(name.value);  // Get value: 'John'\nname.value = 'Jane';      // Set value: triggers updates\n```\n\n### Computed Values\n```javascript\nconst counter = signal(5);\n// Updates when counter changes\nconst double = computed(() =\u003e counter.value * 2);\n\nconsole.log(double.value);  // 10\n\nconst firstName = signal('John');\nconst lastName = signal('Doe');\n\n// Updates whenever firstName or lastName changes\nconst fullName = computed(() =\u003e `${firstName.value} ${lastName.value}`);\nconsole.log(fullName.value);  // 'John Doe'\n```\n\n### Effects\n```javascript\neffect(() =\u003e {\n    // This runs automatically when name.value changes\n    console.log(`Name changed to: ${name.value}`);\n    \n    // Optional cleanup function\n    return () =\u003e {\n        // Cleanup code here\n    };\n});\n```\n\n### Manual Subscriptions\n```javascript\nconst counter = signal(0);\n\n// Subscribe to changes\nconst unsubscribe = counter.subscribe(() =\u003e {\n    console.log('Counter changed:', counter.value);\n});\n\ncounter.value = 1;  // Logs: \"Counter changed: 1\"\n\n// Stop listening to changes\nunsubscribe();\n```\n\n## Usage\n\n### Basic Component\n```javascript\nimport { LightningElement } from 'lwc';\nimport { WithSignals, signal } from 'c/signals';\n\nexport default class Counter extends WithSignals(LightningElement) {\n    count = signal(0);\n    \n    increment() {\n        this.count.value++;\n    }\n    \n    get doubleCount() {\n        return this.count.value * 2;\n    }\n}\n```\n\n```html\n\u003ctemplate\u003e\n    \u003cdiv\u003e\n        \u003cp\u003eCount: {count.value}\u003c/p\u003e\n        \u003cp\u003eDouble: {doubleCount}\u003c/p\u003e\n        \u003cbutton onclick={increment}\u003eIncrement\u003c/button\u003e\n    \u003c/div\u003e\n\u003c/template\u003e\n```\n\n### Parent-Child Communication\n```javascript\n// parent.js\nimport { LightningElement } from 'lwc';\nimport { WithSignals, signal } from 'c/signals';\n\n// Signal shared between components\nexport const parentData = signal('parent data');\n\nexport default class Parent extends WithSignals(LightningElement) {\n    updateData(event) {\n        parentData.value = event.target.value;\n    }\n}\n```\n\n```html\n\u003c!-- parent.html --\u003e\n\u003ctemplate\u003e\n    \u003cdiv\u003e\n        \u003cinput value={parentData.value} onchange={updateData} /\u003e\n        \u003cc-child\u003e\u003c/c-child\u003e\n    \u003c/div\u003e\n\u003c/template\u003e\n```\n\n```javascript\n// child.js\nimport { LightningElement } from 'lwc';\nimport { WithSignals } from 'c/signals';\nimport { parentData } from './parent';\n\nexport default class Child extends WithSignals(LightningElement) {\n    // Use the shared signal directly\n    get message() {\n        return parentData.value;\n    }\n}\n```\n\n```html\n\u003c!-- child.html --\u003e\n\u003ctemplate\u003e\n    \u003cdiv\u003e\n        Message from parent: {message}\n    \u003c/div\u003e\n\u003c/template\u003e\n```\n\n### Global State\n```javascript\n// store/userStore.js\nimport { signal, computed } from 'c/signals';\n\nexport const user = signal({\n    name: 'John',\n    theme: 'light'\n});\n\nexport const isAdmin = computed(() =\u003e user.value.role === 'admin');\n\nexport const updateTheme = (theme) =\u003e {\n    user.value.theme = theme;\n};\n```\n\n```javascript\n// header.js\nimport { LightningElement } from 'lwc';\nimport { WithSignals } from 'c/signals';\nimport { user, updateTheme } from './store/userStore';\n\nexport default class Header extends WithSignals(LightningElement) {\n    // You can access global signals directly in the template\n    get userName() {\n        return user.value.name;\n    }\n\n    get theme() {\n        return user.value.theme;\n    }\n\n    toggleTheme() {\n        updateTheme(this.theme === 'light' ? 'dark' : 'light');\n    }\n}\n```\n\n```javascript\n// settings.js\nimport { LightningElement } from 'lwc';\nimport { WithSignals } from 'c/signals';\nimport { user, isAdmin } from './store/userStore';\n\nexport default class Settings extends WithSignals(LightningElement) {\n    // Global signals and computed values can be used anywhere\n    get showAdminPanel() {\n        return isAdmin.value;\n    }\n\n    updateName(event) {\n        user.value.name = event.target.value;\n    }\n}\n```\n\n### Deep Reactivity\n```javascript\nconst user = signal({\n    name: 'John',\n    settings: { theme: 'dark' }\n});\n\n// Direct property mutations work!\nuser.value.settings.theme = 'light';\n\nconst list = signal([]);\n// Array methods are fully reactive\nlist.value.push('item');\nlist.value.unshift('first');\nlist.value[1] = 'updated';\n```\n\n### Effects auto-dispose\n```javascript\nimport { LightningElement } from 'lwc';\nimport { WithSignals, effect } from 'c/signals';\n\nexport default class Component extends WithSignals(LightningElement) {\n    connectedCallback() {\n        effect(() =\u003e {\n            console.log(\"Effect created.\");\n\n            return () =\u003e {\n                console.log(\"Effect disposed.\"); // Automatically called when the component is disconnected\n            }\n        })\n    }\n}\n```\n\n### Considerations\n\nFor components using the `WithSignals` mixin, it's crucial to maintain proper lifecycle behavior by following specific requirements.\n\nHere's what you need to know:\n\n1. **constructor**:\nAlways call `super()` as the first statement in your constructor. This ensures proper initialization of both the `LightningElement` base class and signals functionality.\n2. **render**:\nYou must call `super.__triggerSignals()` before returning your template. This method ensures that all signal updates are properly processed before the component renders.\n3. **renderedCallback**:\nWhen overriding `renderedCallback()`, always include `super.renderedCallback()`. This maintains the parent class's rendering lifecycle behavior while adding your custom logic.\n4. **disconnectedCallback**:\nInclude `super.disconnectedCallback()` when implementing `disconnectedCallback()`. This ensures proper cleanup of signal subscriptions, effects and prevents memory leaks.\n\n```javascript\nimport { LightningElement } from 'lwc';\nimport template from \"./template.html\";\nimport { WithSignals } from 'c/signals';\n\nexport default class Component extends WithSignals(LightningElement) {\n    constructor() {\n        super(); // Required: Initialize parent class\n    }\n\n    render() {\n        super.__triggerSignals(); // Required: Process signal updates\n\n        return template;\n    }\n\n    renderedCallback() {\n        super.renderedCallback(); // Required: Maintain parent lifecycle\n        // Your custom logic here\n    }\n\n    disconnectedCallback() {\n        super.disconnectedCallback(); // Required: Clean up signals and effects\n        // Your cleanup code here\n    }\n}\n```\n\n## Documentation\n\n- [Architecture and Internal Concepts](./docs/architecture.md)\n\n## License\n\nMIT © Leandro Brunner\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleandrobrunner%2Flwc-signals","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fleandrobrunner%2Flwc-signals","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleandrobrunner%2Flwc-signals/lists"}