Skip to content
Back to skills

186 Tier 2d20b1f7

ASecurity

> Modern state management patterns for Lightning Web Components using @lwc/state and Platform State Managers

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
developmentjavascriptgojavabashreacttestingapiperformancedocumentation

Works with

  • api

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 186-tier_2d20b1f7 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 186 Tier 2d20b1f7?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 186 Tier 2d20b1f7
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-186-tier-2d20b1f7/badge)](https://www.skillsdirectory.com/skills/tools-only-186-tier-2d20b1f7)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
<!-- TIER: 3 | DETAILED REFERENCE -->
<!-- Read after: SKILL.md -->
<!-- Purpose: Modern state management patterns using @lwc/state -->

# LWC State Management

> Modern state management patterns for Lightning Web Components using @lwc/state and Platform State Managers

## Overview

LWC state management has evolved beyond simple reactive properties. This guide covers modern patterns for managing complex state across components, including the `@lwc/state` library and Salesforce Platform State Managers.

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                    STATE MANAGEMENT SPECTRUM                                 │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  SIMPLE ◄────────────────────────────────────────────────────────► COMPLEX │
│                                                                             │
│  @track/@api      Singleton Store      @lwc/state       Platform State     │
│  (Component)      (Cross-Component)    (Full Library)   (Record/Layout)    │
│                                                                             │
│  ┌─────────┐      ┌─────────────┐      ┌────────────┐   ┌───────────────┐  │
│  │ Single  │      │ Shared      │      │ Atoms +    │   │ Record Data + │  │
│  │ Component│      │ Across      │      │ Computed + │   │ Layout State │  │
│  │ State   │      │ Components  │      │ Actions    │   │ (Platform)   │  │
│  └─────────┘      └─────────────┘      └────────────┘   └───────────────┘  │
│                                                                             │
│  Use when:         Use when:            Use when:        Use when:          │
│  - Local state     - Shared cart        - Complex UI     - Record pages     │
│  - Form fields     - User prefs         - Async state    - Flexipages       │
│  - UI toggles      - Cached data        - Derived data   - Tab persistence  │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
```

---

## When to Use Each Pattern

| Pattern | Complexity | Scope | Use Case |
|---------|------------|-------|----------|
| **@track / reactive properties** | Low | Component | Form inputs, toggles, local UI state |
| **Singleton Store** | Medium | Cross-component | Shopping cart, filters, user preferences |
| **@lwc/state** | Medium-High | Cross-component | Complex forms, async state, computed values |
| **Platform State Managers** | High | Page/Record | Record pages, layout-aware components |

---

## Pattern 1: Reactive Properties (Component-Level)

### Standard Reactivity

```javascript
import { LightningElement } from 'lwc';

export default class SimpleState extends LightningElement {
    // Reactive by default (primitive types and objects)
    counter = 0;
    isActive = false;
    user = { name: 'John', email: 'john@example.com' };

    // Object reassignment triggers reactivity
    updateUser() {
        // ✅ Works - new object reference
        this.user = { ...this.user, name: 'Jane' };

        // ❌ Won't trigger rerender - same reference
        // this.user.name = 'Jane';
    }

    increment() {
        this.counter++; // ✅ Primitive assignment is reactive
    }
}
```

### Getters (Computed Properties)

```javascript
export default class ComputedExample extends LightningElement {
    firstName = '';
    lastName = '';
    items = [];

    // Computed property - recalculates when dependencies change
    get fullName() {
        return `${this.firstName} ${this.lastName}`.trim();
    }

    get hasItems() {
        return this.items.length > 0;
    }

    get totalPrice() {
        return this.items.reduce((sum, item) => sum + item.price, 0);
    }

    get formattedPrice() {
        return new Intl.NumberFormat('en-US', {
            style: 'currency',
            currency: 'USD'
        }).format(this.totalPrice);
    }
}
```

---

## Pattern 2: Singleton Store (Cross-Component State)

For sharing state across components without platform dependencies.

### Store Module (`store.js`)

```javascript
/**
 * Singleton store for cross-component state management.
 *
 * Usage:
 * import store from 'c/store';
 *
 * // Read state
 * const cart = store.getState('cart');
 *
 * // Update state
 * store.setState('cart', { items: [...cart.items, newItem] });
 *
 * // Subscribe to changes
 * store.subscribe('cart', (newCart) => { this.cart = newCart; });
 */

// Private state container
const state = new Map();

// Subscribers by key
const subscribers = new Map();

// Get current state for a key
function getState(key) {
    return state.get(key);
}

// Set state and notify subscribers
function setState(key, value) {
    const oldValue = state.get(key);
    state.set(key, value);

    // Notify all subscribers for this key
    const keySubscribers = subscribers.get(key) || [];
    keySubscribers.forEach(callback => {
        try {
            callback(value, oldValue);
        } catch (e) {
            console.error('Store subscriber error:', e);
        }
    });
}

// Subscribe to state changes
function subscribe(key, callback) {
    if (!subscribers.has(key)) {
        subscribers.set(key, []);
    }
    subscribers.get(key).push(callback);

    // Return unsubscribe function
    return () => {
        const keySubscribers = subscribers.get(key) || [];
        const index = keySubscribers.indexOf(callback);
        if (index > -1) {
            keySubscribers.splice(index, 1);
        }
    };
}

// Initialize state with default values
function initState(key, defaultValue) {
    if (!state.has(key)) {
        state.set(key, defaultValue);
    }
    return state.get(key);
}

// Clear state (useful for testing)
function clearState() {
    state.clear();
    subscribers.clear();
}

export default {
    getState,
    setState,
    subscribe,
    initState,
    clearState
};
```

### Using the Store

```javascript
// cartManager.js
import { LightningElement } from 'lwc';
import store from 'c/store';

export default class CartManager extends LightningElement {
    cart = { items: [], total: 0 };
    unsubscribe;

    connectedCallback() {
        // Initialize cart state
        this.cart = store.initState('cart', { items: [], total: 0 });

        // Subscribe to cart changes from other components
        this.unsubscribe = store.subscribe('cart', (newCart) => {
            this.cart = newCart;
        });
    }

    disconnectedCallback() {
        // Clean up subscription
        if (this.unsubscribe) {
            this.unsubscribe();
        }
    }

    addItem(event) {
        const item = event.detail;
        const currentCart = store.getState('cart');

        const newCart = {
            items: [...currentCart.items, item],
            total: currentCart.total + item.price
        };

        store.setState('cart', newCart);
    }
}
```

---

## Pattern 3: @lwc/state Library

The `@lwc/state` library provides reactive state primitives with automatic dependency tracking.

### Installation

```bash
# If using npm in LWC project
npm install @lwc/state
```

### Core Concepts

#### Atoms (Primitive State)

```javascript
import { atom, computed } from '@lwc/state';

// Create atoms for primitive state
const countAtom = atom(0);
const nameAtom = atom('');
const itemsAtom = atom([]);

// Read value
console.log(countAtom.value); // 0

// Write value - triggers reactivity
countAtom.value = 5;

// Reset to initial value
countAtom.reset();
```

#### Computed (Derived State)

```javascript
import { atom, computed } from '@lwc/state';

const priceAtom = atom(100);
const quantityAtom = atom(2);
const taxRateAtom = atom(0.08);

// Computed automatically recalculates when dependencies change
const subtotal = computed(() => priceAtom.value * quantityAtom.value);
const tax = computed(() => subtotal.value * taxRateAtom.value);
const total = computed(() => subtotal.value + tax.value);

console.log(total.value); // 216 (100 * 2 * 1.08)

// Update a dependency - all computed values update
priceAtom.value = 150;
console.log(total.value); // 324 (150 * 2 * 1.08)
```

#### Actions (State Mutations)

```javascript
import { atom, action } from '@lwc/state';

const cartAtom = atom({ items: [], total: 0 });

// Actions encapsulate state mutations
const addToCart = action((item) => {
    const cart = cartAtom.value;
    cartAtom.value = {
        items: [...cart.items, item],
        total: cart.total + item.price
    };
});

const removeFromCart = action((itemId) => {
    const cart = cartAtom.value;
    const item = cart.items.find(i => i.id === itemId);
    cartAtom.value = {
        items: cart.items.filter(i => i.id !== itemId),
        total: cart.total - (item?.price || 0)
    };
});

const clearCart = action(() => {
    cartAtom.reset();
});
```

### LWC Integration

```javascript
import { LightningElement } from 'lwc';
import { atom, computed } from '@lwc/state';

// Define atoms outside component (singleton)
const searchTermAtom = atom('');
const resultsAtom = atom([]);
const isLoadingAtom = atom(false);

// Computed values
const hasResults = computed(() => resultsAtom.value.length > 0);
const resultCount = computed(() => resultsAtom.value.length);

export default class SearchComponent extends LightningElement {
    // Bind atoms to component for reactivity
    get searchTerm() {
        return searchTermAtom.value;
    }

    get results() {
        return resultsAtom.value;
    }

    get isLoading() {
        return isLoadingAtom.value;
    }

    get hasResults() {
        return hasResults.value;
    }

    handleSearchChange(event) {
        searchTermAtom.value = event.target.value;
        this.performSearch();
    }

    async performSearch() {
        if (searchTermAtom.value.length < 2) {
            resultsAtom.value = [];
            return;
        }

        isLoadingAtom.value = true;
        try {
            const results = await searchRecords({ term: searchTermAtom.value });
            resultsAtom.value = results;
        } finally {
            isLoadingAtom.value = false;
        }
    }
}
```

---

## Pattern 4: Platform State Managers

Salesforce provides built-in state managers for record pages and layouts.

### stateManagerRecord

Manages record data with automatic refresh and caching.

```javascript
import { LightningElement, wire } from 'lwc';
import { stateManagerRecord } from 'lightning/stateManagerRecord';
import { getRecord } from 'lightning/uiRecordApi';

export default class RecordStateExample extends LightningElement {
    @api recordId;

    // Wire with state manager for enhanced caching
    @wire(stateManagerRecord, {
        recordId: '$recordId',
        fields: ['Account.Name', 'Account.Industry']
    })
    recordState;

    get accountName() {
        return this.recordState?.data?.fields?.Name?.value;
    }

    get industry() {
        return this.recordState?.data?.fields?.Industry?.value;
    }

    // State manager provides loading/error states
    get isLoading() {
        return this.recordState?.loading;
    }

    get hasError() {
        return !!this.recordState?.error;
    }
}
```

### stateManagerLayout

Manages layout-aware state for flexipage components.

```javascript
import { LightningElement, wire } from 'lwc';
import { stateManagerLayout } from 'lightning/stateManagerLayout';

export default class LayoutAwareComponent extends LightningElement {
    @api recordId;
    @api objectApiName;

    // Wire layout state manager
    @wire(stateManagerLayout, {
        recordId: '$recordId',
        objectApiName: '$objectApiName'
    })
    layoutState;

    get isCompact() {
        return this.layoutState?.density === 'compact';
    }

    get visibleFields() {
        return this.layoutState?.fields || [];
    }
}
```

### Composing State Managers

```javascript
import { LightningElement, wire } from 'lwc';
import { stateManagerRecord } from 'lightning/stateManagerRecord';
import { atom, computed } from '@lwc/state';

// Custom state atoms
const selectedTabAtom = atom('details');
const expandedSectionsAtom = atom(new Set(['overview']));

export default class ComposedStateComponent extends LightningElement {
    @api recordId;

    // Platform state for record data
    @wire(stateManagerRecord, {
        recordId: '$recordId',
        fields: ['Account.Name', 'Account.Type', 'Account.Industry']
    })
    recordState;

    // Custom state for UI
    get selectedTab() {
        return selectedTabAtom.value;
    }

    get expandedSections() {
        return expandedSectionsAtom.value;
    }

    isSectionExpanded(sectionId) {
        return expandedSectionsAtom.value.has(sectionId);
    }

    handleTabChange(event) {
        selectedTabAtom.value = event.detail.value;
    }

    handleSectionToggle(event) {
        const sectionId = event.target.dataset.section;
        const sections = new Set(expandedSectionsAtom.value);

        if (sections.has(sectionId)) {
            sections.delete(sectionId);
        } else {
            sections.add(sectionId);
        }

        expandedSectionsAtom.value = sections;
    }
}
```

---

## Anti-Patterns to Avoid

### ❌ BAD: Mutating Objects In Place

```javascript
// DON'T - won't trigger reactivity
this.user.name = 'New Name';
this.items.push(newItem);
```

### ✅ GOOD: Create New References

```javascript
// DO - triggers reactivity
this.user = { ...this.user, name: 'New Name' };
this.items = [...this.items, newItem];
```

### ❌ BAD: Heavy Computation in Getters

```javascript
// DON'T - runs every render cycle
get expensiveComputation() {
    return this.items
        .map(item => complexTransform(item))
        .filter(item => complexFilter(item))
        .sort((a, b) => complexSort(a, b));
}
```

### ✅ GOOD: Cache Computed Values

```javascript
_cachedResult;
_lastItemsHash;

get optimizedComputation() {
    const currentHash = JSON.stringify(this.items);
    if (this._lastItemsHash !== currentHash) {
        this._cachedResult = this.items
            .map(item => complexTransform(item))
            .filter(item => complexFilter(item))
            .sort((a, b) => complexSort(a, b));
        this._lastItemsHash = currentHash;
    }
    return this._cachedResult;
}
```

### ❌ BAD: Forgetting to Unsubscribe

```javascript
// DON'T - memory leak
connectedCallback() {
    store.subscribe('data', (data) => this.data = data);
}
```

### ✅ GOOD: Clean Up Subscriptions

```javascript
// DO - proper cleanup
_unsubscribe;

connectedCallback() {
    this._unsubscribe = store.subscribe('data', (data) => this.data = data);
}

disconnectedCallback() {
    if (this._unsubscribe) {
        this._unsubscribe();
    }
}
```

---

## Best Practices Summary

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                    STATE MANAGEMENT BEST PRACTICES                           │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  CHOOSE THE RIGHT PATTERN                                                   │
│  ─────────────────────────────────────────────────────────────────────────  │
│  ✅ Start with simple reactive properties                                   │
│  ✅ Use singleton store for shared non-record state                         │
│  ✅ Use @lwc/state for complex derived state                                │
│  ✅ Use Platform State Managers on record pages                             │
│  ❌ Don't over-engineer simple components                                   │
│                                                                             │
│  REACTIVITY                                                                 │
│  ─────────────────────────────────────────────────────────────────────────  │
│  ✅ Create new object/array references for updates                          │
│  ✅ Use spread operator: { ...obj, newProp }                                │
│  ✅ Use getters for computed values                                         │
│  ❌ Don't mutate objects/arrays in place                                    │
│                                                                             │
│  PERFORMANCE                                                                │
│  ─────────────────────────────────────────────────────────────────────────  │
│  ✅ Cache expensive computations                                            │
│  ✅ Debounce rapid state updates                                            │
│  ✅ Clean up subscriptions in disconnectedCallback                          │
│  ❌ Don't do heavy computation in getters                                   │
│                                                                             │
│  TESTING                                                                    │
│  ─────────────────────────────────────────────────────────────────────────  │
│  ✅ Test state transitions explicitly                                       │
│  ✅ Verify subscription cleanup                                             │
│  ✅ Mock store for unit tests                                               │
│  ❌ Don't test implementation details                                       │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
```

---

## Related Documentation

- [SKILL.md](../SKILL.md) - PICKLES Framework overview
- [lwc-best-practices.md](lwc-best-practices.md) - General LWC patterns
- [triangle-pattern.md](triangle-pattern.md) - Component composition

---

## Source

> **References**:
> - [Mastering State Management in LWC using @lwc/state](https://salesforcediaries.com/2025/11/26/mastering-state-management-in-lwc-using-lwc-state/) - Salesforce Diaries
> - [Platform State Managers in LWC](https://salesforcediaries.com/2025/11/26/platform-state-managers-in-lwc/) - Salesforce Diaries

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.