LEARN · DEBUGGING GUIDE

Svelte Store Not Updating Component – Reactive Assignment Pitfalls

If your Svelte component isn't re-rendering when a store value changes, you're likely mutating state without a reactive assignment or misusing the store contract. Here's exactly how to find and fix it.

IntermediateSvelte7 min read

What this usually means

Svelte's reactivity is based on assignments (the = operator). If you mutate an object or array inside a store without reassigning the store value, Svelte cannot detect the change. This is the most common cause. Other possibilities include: not using the $ prefix in reactive statements, using a regular variable instead of a store, or having a subscription that doesn't trigger due to incorrect lifecycle (e.g., not unsubscribing, causing memory leaks but also missed updates). Also, if you use a custom store with a subscribe method that doesn't notify subscribers, the component won't update.

( 01 )Fast diagnosis

The first ten minutes — establish facts before touching code.

  • 1Check if the store mutation uses assignment: `store.set(newValue)` or `store.update(n => n+1)` vs direct mutation like `store.value.push(item)`. The latter will NOT trigger reactivity.
  • 2In the component, verify you're using `$store` syntax in the template or reactive statements. If you use `store.subscribe` manually, ensure you call `unsubscribe()` in `onDestroy`.
  • 3Open browser DevTools and log the store value inside a reactive block: `$: console.log($store)`. If the log doesn't fire when you expect changes, the store isn't being updated correctly.
  • 4Try forcing a re-render by reassigning the store to itself: `store.update(v => v)`. If the component updates, you have a mutation without reassignment.
  • 5Check if the component is still mounted. If it's destroyed and recreated (e.g., due to `{#if}` block), it may reinitialize with stale data. Use `onMount` to re-subscribe if needed.
  • 6Inspect the store definition: if it's a custom store, ensure the `subscribe` method calls `run` when the value changes. Test with a simple writable store to isolate the issue.
( 02 )Where to look

The specific files, logs, configs, and dashboards that usually own this bug.

  • searchComponent.svelte – search for `$store` usage, reactive statements, and event handlers that modify the store.
  • searchStore file (e.g., store.ts or store.js) – look at how the store is created and whether it uses `writable` or custom logic.
  • searchParent or sibling components that also modify the store – ensure they're using `store.set` or `store.update`.
  • searchPackage.json – check for outdated Svelte version (reactivity bugs were fixed in 3.x).
  • searchBrowser DevTools Console – look for errors like 'Cannot assign to read only property' or subscription warnings.
  • searchSvelte DevTools extension – inspect store values and component reactivity status.
  • searchNetwork tab – if store values come from API, verify the response is correct and triggers store update.
( 03 )Common root causes

Practical causes, not theory. These are the things you will actually find.

  • warningMutating store value directly (e.g., `$store.items.push(newItem)`) without reassigning the store.
  • warningUsing a regular variable instead of a store (e.g., `let x = writable(0)` but then using `x` instead of `$x` in template).
  • warningCustom store's subscribe method not calling `run` on every change (e.g., missing call in set/update).
  • warningCalling `store.set` or `store.update` inside a callback that runs after component is destroyed (e.g., async timeout, unresolved promise).
  • warningUsing `$store` inside a reactive statement that has no dependencies (e.g., `$: console.log($store)` outside any function works, but if you wrap it in a function, it won't be reactive).
  • warningMultiple instances of the same store (e.g., importing the store in different files and inadvertently creating new instances).
  • warningUsing `{#key}` blocks that destroy and recreate the component, causing loss of subscription.
( 04 )Fix patterns

Concrete fix directions. Pick the one that matches your root cause.

  • buildReplace direct mutation with `store.update(current => { current.items.push(newItem); return current; })` or create a new object: `store.set([...$store, newItem])`.
  • buildIf you must mutate, use `store.set` after mutation: `$store.items.push(newItem); store.set($store);` (but better to use update).
  • buildFor custom stores, ensure the subscribe method calls `run` with the new value in set/update.
  • buildIn components, always use the `$store` syntax for automatic subscription. If you use manual subscribe, call `unsubscribe()` in `onDestroy`.
  • buildUse `derived` stores for computed values that depend on other stores, and ensure dependencies are listed correctly.
  • buildFor async operations, update the store only after the component is still mounted (check with `onDestroy` flag or use `cancellable` pattern).
( 05 )How to verify

A fix you cannot prove is a guess. Close the loop.

  • verifiedAfter fix, the component re-renders immediately when store value changes (watch DOM update).
  • verifiedLog $store inside a reactive block and see new value appear on every change.
  • verifiedUse Svelte DevTools to see the store value update and component re-render count increment.
  • verifiedWrite a test: change store value programmatically and assert that the component's rendered output changes.
  • verifiedCheck for memory leaks: navigate away and back, ensure no stale subscriptions (no multiple console logs).
( 06 )Mistakes to avoid

Things that make this bug worse or harder to find.

  • warningDon't use `Object.assign` or `Array.push` without reassigning – they are mutations.
  • warningDon't forget to import the store correctly; avoid naming collisions with local variables.
  • warningDon't use `$store` inside a function that is not reactive (e.g., inside `on:click` handler, use `store.set()` instead of `$store = newValue` which is shorthand in reactive blocks only).
  • warningDon't rely on `store.subscribe` inside `onMount` without unsubscribing – leads to memory leaks and stale updates.
  • warningDon't use `{#key}` on a component that depends on a store; it may remount and reinitialize, causing double subscriptions or lost state.
( 07 )War story

Cart Component Not Updating After Adding Item

Frontend DeveloperSvelte 3.46, SvelteKit, Tailwind CSS

Timeline

  1. 09:15User reports cart badge shows 0 items after clicking 'Add to Cart'.
  2. 09:18Check console – no errors. Log cart store in Cart.svelte: `$: console.log($cart)` shows empty array after add.
  3. 09:22Inspect addToCart function in ProductCard.svelte. It calls `cart.update(items => { items.push(product); return items; })`.
  4. 09:25I realize: `items.push(product)` mutates the array, but `return items` returns the same reference. Svelte sees same reference, so no update.
  5. 09:28Fix: change to `cart.update(items => { return [...items, product]; })`.
  6. 09:30Test: cart badge updates correctly. Deploy fix.

I was working on an e-commerce SvelteKit app. The cart was backed by a Svelte writable store. The badge component subscribed to $cart.length. After clicking 'Add to Cart', the badge stayed at 0. I checked the store in DevTools – it actually had the items, but the component didn't re-render.

I traced the add function: it used `cart.update(items => { items.push(product); return items; })`. The array was mutated and the same reference returned. Svelte's reactivity depends on assignment – returning the same object reference doesn't trigger subscribers. I changed it to return a new array: `[...items, product]`. That fixed it.

The lesson: never mutate store values. Always return a new object or array. Also, use `$store` syntax in templates to let Svelte handle subscriptions. I now always check for reference equality in store updates.

Root cause

Using `Array.push` inside `store.update` and returning the same array reference, bypassing Svelte's reactivity.

The fix

Replace mutation with immutable update: `cart.update(items => [...items, product])`.

The lesson

Svelte's reactivity is triggered by assignment, not mutation. Always return a new object/array from store updates.

( 08 )How Svelte's Reactivity Works with Stores

Svelte's compiler tracks assignments to reactively update the DOM. When you use `$store` in a component, Svelte compiles it to a subscription. The store's `subscribe` method is called, and the component's update function is called whenever the store value changes. This only happens if the store's `set` or `update` methods are invoked, which in turn call all subscriber callbacks.

If you directly mutate a store's value (e.g., `$store.foo = 'bar'`), you are not calling `store.set`. Svelte does NOT create a setter for store values; `$store` is a read-only reference in the template. The only way to update a store is through its `set` or `update` methods. The `$store` syntax is only for reading and for reactive assignments inside a `$:` block (e.g., `$store = newValue` compiles to `store.set(newValue)`). Outside reactive blocks, `$store = newValue` is not valid – you must use `store.set`.

( 09 )Common Pitfall: Array and Object Mutations

The most frequent cause of 'store not updating' is mutating arrays or objects without reassigning the store. For example: `let items = []; store.set(items); items.push('new');` – this mutation does not call `store.set` again, so no update. Even inside `store.update`, you must return a new object: `store.update(arr => { arr.push(x); return arr; })` still returns the same reference. Instead, do `store.update(arr => [...arr, x])`.

For objects, spread operator helps: `store.update(obj => ({...obj, key: newVal}))`. Svelte's `writable` store uses reference equality to decide whether to notify subscribers. If the reference hasn't changed, it skips the update. This is an optimization, but it means you must create new references.

( 10 )Custom Stores and Subscription Lifecycle

If you create a custom store, you must implement the `subscribe` method correctly. It receives a `run` callback that should be called every time the value changes. A common mistake is to only call `run` once during initialization. Ensure your `set` and `update` methods iterate over all subscribers and call each `run` with the new value.

Also, components automatically unsubscribe when they are destroyed (if using `$store`). But if you manually subscribe via `store.subscribe()` in `onMount`, you must call the returned `unsubscribe` function in `onDestroy`. Failure to do so leads to memory leaks and possibly missed updates if the subscription is called after component is destroyed.

( 11 )Debugging with Svelte DevTools and Logging

Install the Svelte DevTools browser extension. It shows all stores and their current values, plus which components are subscribed. You can see if a store update triggers a re-render. If the store value changes in DevTools but the component doesn't update, the component is likely not subscribed correctly.

Add a reactive log: `$: console.log('store changed', $store)`. This will log every time the store value changes. If the log doesn't fire, the store itself isn't updating. If it fires but the DOM doesn't change, the issue is in the template logic (e.g., using a non-reactive variable).

Frequently asked questions

Why does $store = newValue work in a reactive block but not in an event handler?

In a `$:` reactive block, `$store = newValue` is syntactic sugar for `store.set(newValue)`. But in an event handler or any other function, you must explicitly call `store.set(newValue)`. The `$` prefix is only for reading and reactive assignments.

My store updates when I call store.set() in one component, but another component doesn't see it. What's wrong?

Check that both components are using the same store instance. If you import the store from a module, it should be a singleton. If you define the store inside a component or a function, each call creates a new instance. Also ensure the second component is still mounted – if it's conditionally rendered, it may remount and reinitialize.

Can I use $store inside an if block or each block?

Yes, `$store` works anywhere in the template, including `{#if}`, `{#each}`, etc. Svelte handles reactivity automatically. Just ensure the store is imported correctly and you're using the `$` prefix.

How do I update a store from an async function?

You can call `store.set(await fetch(...))` inside an async function. Just be aware that if the component unmounts before the async operation completes, you'll get a warning about updating an unmounted component. Use an `abort` controller or a flag to avoid that.