Large data, external libraries and custom reactive sources need more control than plain $state.
Deep proxies are convenient but not free. Svelte gives you finer tools.
- `$state.raw(value)` stores the value without proxying. Only reassignment is reactive (
list = [...list, x]); mutations are ignored. Use it for large arrays or API responses you replace wholesale, and for objects from libraries. - `$state.snapshot(value)` returns a plain, non-proxied deep copy: pass it to
structuredClone,JSON.stringify, charting libraries or anything that dislikes Proxies. - `untrack(fn)` from
sveltereads state inside an effect or derived without making it a dependency. - `svelte/reactivity` provides reactive versions of built-ins:
SvelteMap,SvelteSet,SvelteDate,SvelteURL,SvelteURLSearchParams. A plainMapin$stateis not made reactive, because only plain objects and arrays are proxied. - `MediaQuery` from the same module is a reactive media query (
new MediaQuery('(min-width: 800px)').current). - `createSubscriber(start)` turns any external event source into something reactive: reading it inside an effect subscribes, and the source is started and stopped automatically as readers come and go. It is how
MediaQueryis built.
Equality matters too: setting $state to the same primitive value doesn't trigger updates, and $derived only notifies when its result changes. Class instances (other than those using runes in fields) are not proxied, so store reactive fields on the class itself.
Example
<script>
import { untrack } from 'svelte';
import { SvelteMap, MediaQuery, createSubscriber } from 'svelte/reactivity';
let rows = $state.raw([]); // replace, don't mutate
rows = Array.from({ length: 10_000 }, (_, i) => ({ id: i }));
const votes = new SvelteMap(); // reactive Map
const wide = new MediaQuery('(min-width: 800px)');
let count = $state(0), logged = $state(0);
$effect(() => console.log(count, untrack(() => logged))); // depends on count only
// Custom reactive source: is the tab visible?
const subscribe = createSubscriber((update) => {
document.addEventListener('visibilitychange', update);
return () => document.removeEventListener('visibilitychange', update);
});
const visible = { get current() { subscribe(); return !document.hidden; } };
</script>
<button onclick={() => votes.set('svelte', (votes.get('svelte') ?? 0) + 1)}>
vote ({votes.get('svelte') ?? 0})</button>
<p>{rows.length} rows, {wide.current ? 'wide' : 'narrow'}, visible: {visible.current}</p>Remember
$state.rawskips proxying: reassign to update$state.snapshotgives a plain copy for libraries and serialisation- Use
SvelteMap/SvelteSet/SvelteURLfor reactive built-ins;untrackto avoid dependencies createSubscriberadapts external event sources
Watch out for
- Putting a
Mapin$stateand calling.set(), expecting updates; useSvelteMap.