Guides expert-level svelte 5 runes patterns implementation: javascript and frameworks decision frameworks, production-ready patterns, and concrete templates for svelte runes patterns workflows. Use when the user asks about svelte 5 runes patterns, svelte runes patterns configuration, or javascript best practices for svelte projects. Do NOT use when the user needs a different web development capability -- check sibling skills in the web development subcategory.
Scanned 9/2/2026
Install to Claude Code
npx -y skills add FerroxLabs/wayland --skill svelte-runes-patterns --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Svelte Runes Patterns?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/ferroxlabs-svelte-runes-patterns)More formats (shields.io, HTML) on the badges page.
---
name: svelte-runes-patterns
description: |
Guides expert-level svelte 5 runes patterns implementation: javascript and frameworks decision frameworks, production-ready patterns, and concrete templates for svelte runes patterns workflows.
Use when the user asks about svelte 5 runes patterns, svelte runes patterns configuration, or javascript best practices for svelte projects.
Do NOT use when the user needs a different web development capability -- check sibling skills in the web development subcategory.
license: Apache-2.0
metadata:
author: foundry-skills
version: "1.0.0"
tags: "javascript frameworks frontend best-practices"
category: "web-development"
subcategory: "web-development"
depends: ""
disclaimer: "none"
difficulty: "intermediate"
---
# Svelte Runes Patterns
## When to Use
**Use this skill when:**
- The user is building a Svelte 5 component and asks how to manage reactive state using `$state`, `$derived`, or `$effect`
- The user is migrating from Svelte 4 stores (`writable`, `readable`, `derived`) to Svelte 5 runes and needs a conversion strategy
- The user asks about cross-component state sharing in Svelte 5 without a global store library
- The user wants to understand when to use `$effect` versus `$derived` versus event handlers for side effects
- The user is experiencing infinite reactive loops, stale values, or unexpected re-renders in a Svelte 5 application
- The user asks how to write reactive class instances, context-based state, or universal (isomorphic) reactive logic in Svelte 5
- The user wants to implement fine-grained reactivity for performance-sensitive UIs (large lists, real-time dashboards, canvas-driven interfaces)
- The user needs to lift state out of a component while preserving reactivity
**Do NOT use this skill when:**
- The user is on Svelte 4 or earlier and has not migrated -- use a Svelte 4 stores skill instead
- The user needs SvelteKit routing, server-side rendering, or `load` function patterns -- those are SvelteKit-specific concerns
- The user is asking about Svelte component composition patterns (slots, snippets, props API design) unrelated to reactivity
- The user needs build tooling configuration (Vite, SvelteKit adapter setup) -- check the build tooling skill
- The user is asking about testing Svelte components with Vitest or Playwright -- check the component testing skill
- The user needs TypeScript generic typing of Svelte components -- check the Svelte TypeScript skill
- The user is asking about general JavaScript reactivity patterns outside of Svelte (signals, MobX, Vue reactivity)
- The user needs CSS or animation patterns in Svelte -- check the Svelte transitions and animations skill
---
## Process
### 1. Identify the Reactivity Problem Category
Before writing any code, classify the user's problem into one of five categories. Each maps to a different rune strategy.
- **Local component state** -- a value that is owned and mutated only inside one component. Always reach for `$state` first.
- **Derived computation** -- a value that is always a pure function of other reactive values. Use `$derived` or `$derived.by` for complex multi-expression logic.
- **Side effects that synchronize with reactive state** -- DOM manipulations, `fetch` calls, subscriptions. Use `$effect` with explicit awareness of its dependency tracking rules.
- **Shared cross-component state** -- state that multiple unrelated components need to read and write. Use reactive class instances exported from `.svelte.js` modules, or Svelte context with `$state` at the parent.
- **Props and bindable values** -- data flowing in from parents or two-way bound to child components. Use `$props()` with optional destructuring defaults, and `$bindable()` for two-way bindings.
### 2. Apply the Correct Rune Primitive
For each category, use the following decision rules:
- **`$state(value)`** -- creates a deeply reactive proxy for objects and arrays. Mutations to nested properties (e.g., `list.push(item)`, `obj.name = "x"`) are tracked automatically. Use primitives directly: `let count = $state(0)`.
- **`$state.raw(value)`** -- creates a shallowly reactive value. Reassignment triggers updates; mutation does not. Use this for large data structures where you replace the whole value (e.g., sorted arrays, paginated data) and do not want deep proxy overhead.
- **`$derived(expr)`** -- a synchronous, lazy computation. Svelte caches the value and recomputes only when dependencies change. The expression must be side-effect-free.
- **`$derived.by(() => { ... })`** -- use when the derivation requires multiple statements, intermediate variables, or control flow (loops, conditionals). Still must be pure.
- **`$effect(() => { ... })`** -- runs after the component mounts and after each reactive update. Tracks any `$state` or `$derived` values accessed synchronously inside it. Return a cleanup function to tear down subscriptions or timers.
- **`$effect.pre(() => { ... })`** -- runs before the DOM is updated. Use for reading DOM layout measurements that need to happen before a paint, such as calculating element height before an animation.
- **`$props()`** -- destructure component props. Provide defaults inline: `let { count = 0, label = "Click me" } = $props()`. Props are read-only inside the child by default.
- **`$bindable(default)`** -- marks a prop as two-way bindable. Wrap the default value: `let { value = $bindable("") } = $props()`. The parent can then use `bind:value`.
- **`$inspect(value)`** -- development-only rune that logs reactive value changes with a stack trace. Remove before production. Accepts multiple arguments: `$inspect(count, list)`.
### 3. Structure Shared State Using `.svelte.js` Modules
When state must be shared across the component tree without threading props, follow this pattern:
- Create a file with the `.svelte.js` extension (or `.svelte.ts` for TypeScript). Runes are available in these files at the module level.
- Define a reactive class or a factory function that returns reactive objects. Do NOT export bare `$state` variables at module level if you need encapsulation -- they become module singletons and cannot be reset per-instance.
- For per-subtree state (not truly global), pass instances through Svelte context (`setContext` / `getContext`) with a typed symbol key.
- For truly global singletons (theme, auth, feature flags), export a class instance or a frozen reactive object.
```javascript
// counter.svelte.js
export class Counter {
count = $state(0);
step = $state(1);
get doubled() {
return $derived.by(() => this.count * 2);
}
increment() {
this.count += this.step;
}
reset() {
this.count = 0;
}
}
```
- Do NOT use `$effect` inside class constructors -- class instances may be created outside the component lifecycle. Effects belong in component `<script>` blocks or in explicit setup/teardown methods called by the component.
### 4. Design Effects Correctly
`$effect` is the most commonly misused rune. Apply these rules precisely:
- **Dependency tracking is synchronous and automatic.** Any `$state` or `$derived` value read synchronously inside an effect body is tracked. Values read inside `setTimeout`, `Promise.then`, or event callbacks are NOT tracked.
- **Avoid writing to `$state` inside `$effect`.** This creates feedback loops. If you write to state inside an effect, Svelte reschedules the effect, which may write again -- an infinite loop. Use `$derived` instead when the pattern is "state A always determines state B".
- **Return a cleanup function** whenever the effect subscribes to external resources:
```javascript
$effect(() => {
const controller = new AbortController();
fetch(url, { signal: controller.signal })
.then(r => r.json())
.then(data => { result = data; });
return () => controller.abort();
});
```
- **`$effect` runs after mount.** It does NOT run during server-side rendering. If you need SSR-safe initialization, do it in module-level code or inside `$derived`.
- **Batch multiple related effects** -- if two effects always run together and share dependencies, combine them into one. Separate effects for orthogonal concerns.
### 5. Handle Props and Two-Way Binding
- Always destructure `$props()` at the top of the script block, before any other reactive declarations.
- Provide sensible defaults for every prop to avoid undefined errors: `let { items = [], onSelect = () => {} } = $props()`.
- Use `$bindable()` sparingly -- only when the parent genuinely needs two-way synchronization (form inputs, controlled components). Prefer callback props (`onChange`) for one-directional data flow when that is clearer.
- For rest props (spread attributes to a root element), use the rest syntax: `let { class: className, ...attrs } = $props()`.
- Do NOT destructure props with renaming and then attempt to reassign -- `$bindable` is required for any prop the component writes back to.
### 6. Migrate from Svelte 4 Stores
Map each Svelte 4 store type to its Svelte 5 rune equivalent:
| Svelte 4 | Svelte 5 Equivalent | Notes |
|---|---|---|
| `writable(value)` | `$state(value)` in `.svelte.js` | Exported from a shared module |
| `readable(value, start)` | `$state` + `$effect` setup/teardown | Effect handles the subscription |
| `derived(store, fn)` | `$derived(() => fn(value))` | Pure function of other `$state` |
| `get(store)` | Direct variable access | No `get()` helper needed |
| `$store` auto-subscription | Direct variable access | No `$` prefix needed in runes mode |
| `store.update(fn)` | `value = fn(value)` or method call | Mutate directly |
- Svelte 4 stores still work in Svelte 5 components. Migrate incrementally -- start with new state, then port store-dependent logic.
- In runes mode (files with `<svelte:options runes={true} />` or the project-level `runes: true` Svelte config), the `$` auto-subscription syntax is unavailable.
### 7. Validate and Debug Reactive Graphs
- Use `$inspect(value)` during development to trace when and why values change. It prints the value and a stack trace on every change.
- Watch for **diamond dependencies** -- `$derived` A and `$derived` B both depend on `$state` X; `$derived` C depends on both A and B. Svelte's fine-grained reactivity resolves these correctly, but be aware that a single change to X triggers a single synchronous recomputation, not a cascade.
- If a component re-renders more than expected, add `$inspect` to each `$derived` value to identify which dependency is changing.
- Check for accidental reactivity: accessing a `$state` object inside a non-reactive context (a regular function called outside an effect or template) returns the current value but does NOT subscribe. This is intentional -- only template expressions, `$derived`, and `$effect` bodies track dependencies.
- For TypeScript projects, `$state`, `$derived`, and `$props` are fully typed by inference. Annotate explicitly when inference fails: `let items = $state<Item[]>([])`.
### 8. Apply Performance Patterns for Large-Scale UIs
- Use `$state.raw` for arrays that are always replaced wholesale (sorted results, paginated pages). The deep proxy from `$state` has overhead on large arrays accessed in tight loops.
- Prefer `$derived` over `$effect` for computed values -- `$derived` is lazy (only computes when read) while `$effect` runs eagerly on every change.
- For list rendering, assign stable keys using `{#each items as item (item.id)}`. Without a key, Svelte destroys and recreates DOM nodes on reorder.
- For very large lists (1000+ items), combine `$state.raw` with virtualization libraries (e.g., svelte-virtual or a custom `IntersectionObserver`-based solution) -- runes do not automatically virtualize DOM.
- Avoid creating new objects or arrays inside `$derived` expressions if they are compared by reference downstream -- the derived value changes on every computation even if the data is identical. Use a stable reference or memoize with a custom equality check.
---
## Output Format
When helping a user with a Svelte 5 runes question, structure your response as follows:
```
## Problem Classification
[One sentence identifying which reactivity category this falls into]
## Recommended Rune(s)
[The specific rune(s) to use and why, with trade-offs if alternatives exist]
## Implementation
### Component Code
```svelte
<script>
// Rune declarations at top
// Props destructuring first ($props)
// State declarations ($state)
// Derived values ($derived)
// Effects last ($effect)
</script>
<!-- Template -->
```
### Shared State Module (if applicable)
```javascript
// filename.svelte.js
// Exported class or factory function
```
## Key Decisions
| Decision | Choice | Rationale |
|---|---|---|
| [decision point] | [choice made] | [why] |
## Common Mistakes to Avoid
- [Specific anti-pattern relevant to this use case]
- [Another anti-pattern]
## Migration Note (if applicable)
[How this differs from Svelte 4 and what to update]
```
---
## Rules
1. **NEVER use `$state` at the top level of a `.svelte.js` module without wrapping it in a class or function** -- top-level module state is a true singleton shared across all component instances and all users in SSR environments. This causes state leakage between requests.
2. **NEVER write to reactive state inside `$derived`** -- `$derived` must be a pure computation. Writing to `$state` inside a `$derived` block causes undefined behavior and Svelte will warn in development. If you need to produce a side effect, use `$effect`.
3. **ALWAYS return a cleanup function from `$effect` when it creates subscriptions, timers, or event listeners** -- failing to clean up causes memory leaks and stale closures that update state after the component unmounts.
4. **NEVER read reactive values inside asynchronous callbacks within `$effect` and expect them to be tracked** -- dependency tracking ends at the first `await`. Capture the values synchronously before any async operation if you need to re-run the effect when they change.
5. **ALWAYS call `$props()` exactly once at the top of the component script block** -- calling it multiple times or inside conditional blocks is not supported and produces errors.
6. **NEVER use `$effect` as a substitute for `$derived`** -- if your effect only reads state and writes another piece of state, that is a derived value. Use `$derived`. Reserve `$effect` for genuine external side effects (DOM, network, subscriptions).
7. **ALWAYS use `$state.raw` for data that is large (objects with 100+ properties, arrays with 1000+ elements) and replaced atomically** -- the deep reactive proxy adds per-property overhead that compounds at scale. Profile with Chrome DevTools Performance tab if unsure.
8. **NEVER access `$inspect` in production builds** -- `$inspect` is a development-only tool. Svelte strips it in production builds automatically, but relying on its output in production code paths is a bug.
9. **ALWAYS use a typed Symbol as the context key when sharing reactive instances via `setContext`/`getContext`** -- string keys collide across library boundaries. Use `const KEY = Symbol('myFeatureContext')` in a shared constants file.
10. **NEVER mutate props directly inside a child component unless the prop is declared with `$bindable()`** -- direct prop mutation is silently ignored or throws a runtime error in strict mode. Use callback props or `$bindable` explicitly.
---
## Edge Cases
### Reactive State in SSR (SvelteKit)
In SvelteKit with server-side rendering, components run on the server where there is no DOM and no persistent component lifecycle. Rules to follow:
- `$effect` and `$effect.pre` do NOT run during SSR. Do not depend on them for initial data fetching -- use SvelteKit `load` functions instead.
- Module-level singletons in `.svelte.js` files are shared across all server requests in a single Node.js process. Never store user-specific data (auth state, session data) in module-level `$state` singletons. Use context or per-request stores.
- `$derived` values run fine on the server since they are pure computations.
- For conditional client-only behavior, check `typeof window !== 'undefined'` inside effects or use the SvelteKit `browser` import from `$app/environment`.
### Infinite Reactive Loops
Symptom: the browser tab freezes or shows a "Maximum update depth exceeded" style error. Cause: an `$effect` that writes to a `$state` value it also reads.
Resolution steps:
1. Identify the effect writing to state. Add `$inspect` to the written value to see how often it changes.
2. Check if the write is conditional -- if the new value equals the current value, Svelte will skip the update, breaking the loop. Add an equality guard: `if (newValue !== currentValue) { currentValue = newValue; }`.
3. Better: determine if the written value is actually a derived value of other state, and replace the effect with `$derived`.
4. If the effect must write to state for a valid side effect (e.g., normalizing user input), use `$effect.pre` with explicit early-exit conditions.
### Migrating a Component Using Multiple Stores
A Svelte 4 component that subscribes to 3+ stores presents a migration challenge because store subscriptions were transparent (`$storeName` syntax). In runes mode:
- Convert each `writable` store to a `.svelte.js` module exporting a class with the equivalent state and methods.
- Import the class and instantiate it -- either at module level (singleton) or in context (per-subtree).
- Replace all `$storeName.property` accesses with direct property access on the imported instance.
- Replace `storeName.update(fn)` calls with direct method calls or property assignments.
- Migrate one store at a time in separate commits. Svelte 4 stores and Svelte 5 runes coexist during migration.
### Class Instances with Reactive Getters
Using `$derived` inside a class body requires care because `$derived` returns a value, not a getter automatically:
```javascript
// WRONG -- $derived is not a class field getter
class Timer {
elapsed = $state(0);
formatted = $derived(`${this.elapsed}s`); // This does not work as expected in a class
}
// CORRECT -- use a getter with $derived.by inside the get block
class Timer {
elapsed = $state(0);
get formatted() {
return `${this.elapsed}s`; // Plain getter, reactive because it reads $state
}
}
```
Plain getters on class instances that read `$state` properties ARE reactive in templates and `$derived` computations because Svelte tracks property access on reactive objects. No `$derived` wrapper is needed inside a class getter that reads `this.someState`.
### Testing Runes-Based State Modules
`.svelte.js` modules with runes cannot be unit-tested with plain Vitest without a Svelte compiler step because `$state`, `$derived`, etc. are compiler transforms, not runtime functions. Solutions:
- Configure `vitest.config.js` to include `.svelte.js` in the Svelte plugin transform list: `svelte({ extensions: ['.svelte', '.svelte.js'] })`.
- Write component-level tests using `@testing-library/svelte` and mount a thin wrapper component that uses the shared state module -- test behavior through the component interface rather than the module directly.
- For pure business logic that does not need reactivity, extract it into a plain `.js` file. Only put reactive state in `.svelte.js`.
### Reactive State with WebSocket or EventSource Subscriptions
Long-lived connections that push data need careful effect lifecycle management:
```javascript
$effect(() => {
const ws = new WebSocket(`wss://api.example.com/feed?channel=${channelId}`);
// channelId must be $state or $derived for the effect to re-run when it changes
ws.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
messages = [...messages, data]; // messages is $state
});
ws.addEventListener('error', () => {
connectionStatus = 'error'; // connectionStatus is $state
});
return () => {
ws.close();
};
});
```
The `channelId` read inside the effect body registers as a dependency. When `channelId` changes, Svelte runs the cleanup (closing the old WebSocket) and re-runs the effect (opening a new one). This is the correct pattern -- do NOT manage connection lifecycle manually with separate open/close effects.
### Runes in Non-Component TypeScript Files (.svelte.ts)
Runes work in `.svelte.ts` files the same way they work in `.svelte.js`. The TypeScript compiler alone cannot process runes -- the Svelte compiler runs first, then TypeScript. Ensure:
- `tsconfig.json` does NOT include `.svelte.js` or `.svelte.ts` files in `include` with `isolatedModules: true` without the Svelte preprocessor in the chain.
- `$state<T>(value)` accepts a type parameter for explicit typing: `let users = $state<User[]>([])`.
- `$derived` infers its return type automatically from the expression.
- `$props()` infers prop types from the destructuring with default values. For complex types, declare an interface and use `$props() as MyProps`.
---
## Example
**Input:** "I'm building a shopping cart feature in Svelte 5. I have a `CartItem` type with `id`, `name`, `price`, and `quantity`. I need: a shared cart state module, a cart item component that can update quantity and remove itself, a cart summary component showing total, and the whole thing needs to work with SvelteKit SSR."
---
**Output:**
## Problem Classification
This is a **shared cross-component state** problem -- the cart state must be readable and mutable from unrelated components (item list, cart summary, header badge count) without prop drilling. SSR compatibility requires avoiding module-level singletons.
## Recommended Runes
- `$state` inside a class for cart item collection (mutable, deeply tracked)
- `$state.raw` is NOT appropriate here -- we mutate individual items (quantity changes) rather than replacing the array wholesale
- Plain class getters for derived totals (reactive without `$derived` wrapper needed inside the class)
- Svelte context (`setContext`/`getContext`) to pass the cart instance per-page-load, preventing SSR request bleeding
## Implementation
### Shared Cart State Module
```javascript
// src/lib/cart.svelte.js
export class Cart {
/** @type {CartItem[]} */
items = $state([]);
get totalItems() {
return this.items.reduce((sum, item) => sum + item.quantity, 0);
}
get subtotal() {
return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
get formattedSubtotal() {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(this.subtotal);
}
/**
* @param {{ id: string, name: string, price: number }} product
*/
addItem(product) {
const existing = this.items.find(i => i.id === product.id);
if (existing) {
existing.quantity += 1;
} else {
this.items.push({ ...product, quantity: 1 });
}
}
/**
* @param {string} id
* @param {number} quantity
*/
updateQuantity(id, quantity) {
if (quantity <= 0) {
this.removeItem(id);
return;
}
const item = this.items.find(i => i.id === id);
if (item) item.quantity = quantity;
}
/** @param {string} id */
removeItem(id) {
this.items = this.items.filter(i => i.id !== id);
}
clear() {
this.items = [];
}
}
```
### Context Setup (SvelteKit Root Layout)
```svelte
<!-- src/routes/+layout.svelte -->
<script>
import { setContext } from 'svelte';
import { Cart } from '$lib/cart.svelte.js';
// Create a new Cart instance per render (SSR-safe -- not a singleton)
const cart = new Cart();
// Use a Symbol key to avoid collisions with library context
export const CART_KEY = Symbol('cart');
setContext(CART_KEY, cart);
let { children } = $props();
</script>
{@render children()}
```
### Context Access Helper
```javascript
// src/lib/useCart.svelte.js
import { getContext } from 'svelte';
import { CART_KEY } from '../routes/+layout.svelte';
/** @returns {import('./cart.svelte.js').Cart} */
export function useCart() {
const cart = getContext(CART_KEY);
if (!cart) {
throw new Error('useCart must be called within a component tree that has setContext(CART_KEY)');
}
return cart;
}
```
### Cart Item Component
```svelte
<!-- src/lib/components/CartItem.svelte -->
<script>
import { useCart } from '$lib/useCart.svelte.js';
let { item } = $props();
// item shape: { id: string, name: string, price: number, quantity: number }
const cart = useCart();
const formattedPrice = $derived(
new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' })
.format(item.price * item.quantity)
);
function handleQuantityChange(event) {
const newQty = parseInt(event.target.value, 10);
if (!isNaN(newQty)) {
cart.updateQuantity(item.id, newQty);
}
}
</script>
<li class="cart-item">
<span class="cart-item__name">{item.name}</span>
<label class="cart-item__qty">
<span class="sr-only">Quantity for {item.name}</span>
<input
type="number"
min="1"
max="99"
value={item.quantity}
onchange={handleQuantityChange}
/>
</label>
<span class="cart-item__price">{formattedPrice}</span>
<button
type="button"
aria-label="Remove {item.name} from cart"
onclick={() => cart.removeItem(item.id)}
>
Remove
</button>
</li>
```
### Cart Summary Component
```svelte
<!-- src/lib/components/CartSummary.svelte -->
<script>
import { useCart } from '$lib/useCart.svelte.js';
import CartItem from './CartItem.svelte';
const cart = useCart();
</script>
{#if cart.items.length === 0}
<p class="cart-summary--empty">Your cart is empty.</p>
{:else}
<section class="cart-summary" aria-label="Shopping cart">
<h2>Cart ({cart.totalItems} {cart.totalItems === 1 ? 'item' : 'items'})</h2>
<ul class="cart-summary__list">
{#each cart.items as item (item.id)}
<CartItem {item} />
{/each}
</ul>
<footer class="cart-summary__footer">
<p class="cart-summary__subtotal">
Subtotal: <strong>{cart.formattedSubtotal}</strong>
</p>
<button type="button" onclick={() => cart.clear()}>Clear cart</button>
<a href="/checkout">Proceed to checkout</a>
</footer>
</section>
{/if}
```
## Key Decisions
| Decision | Choice | Rationale |
|---|---|---|
| Singleton vs. context | Context (`setContext`) | Prevents state leakage between SSR requests -- each render gets a fresh `Cart` instance |
| `$state` vs. `$state.raw` | `$state` | Individual item quantity mutations are tracked; `$state.raw` would miss `.quantity =` writes |
| Derived totals | Plain class getters | Getters reading `$state` properties are reactive in templates without `$derived` wrapper; avoids redundancy |
| `removeItem` via `filter` | Replace array reference | `this.items = this.items.filter(...)` triggers reactivity; `splice` mutates in place and DOES work with `$state`, but `filter` is clearer |
| Quantity update clamping | Delegate to `updateQuantity` | Centralizes the zero-quantity-means-remove rule; components do not implement business logic |
| Formatted currency | `Intl.NumberFormat` in derived | SSR-compatible, locale-aware, no external dependency |
## Common Mistakes to Avoid
- **Do NOT export a `Cart` singleton at module level in `cart.svelte.js`.** `export const cart = new Cart()` creates one cart shared across all SSR requests in the same Node.js process. Different users' carts would be mixed together. Always instantiate in the layout and distribute via context.
- **Do NOT use `$effect` to compute the subtotal** -- the subtotal is a pure function of `items`. Using an effect to write `subtotal = items.reduce(...)` creates unnecessary state and a potential loop. The class getter is always in sync automatically.
- **Do NOT forget stable keys in `{#each}` loops** -- `{#each cart.items as item (item.id)}` is required. Without `(item.id)`, Svelte patches list items by index, destroying and recreating DOM on any reorder or splice, losing input focus and triggering incorrect animations.
- **Do NOT read `cart.items.length` and `cart.items[0].name` in separate template locations without understanding they both track the same reactive array** -- this is fine and correct, but developers sometimes add redundant `$derived` wrappers thinking they need to "cache" the access. They do not.
## Migration Note
In Svelte 4, this cart would be a `writable` store (`writable<CartItem[]>([])`), distributed via context as a plain store. Components subscribed with `$cart`. The migration path is:
1. Replace `writable([])` with a `Cart` class instance containing `items = $state([])`
2. Replace `$cart` auto-subscriptions with direct `cart.items` access
3. Replace `cart.update(items => [...items, newItem])` with `cart.addItem(product)` method calls
4. Remove all `import { writable, derived, get } from 'svelte/store'` imports from the module
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!