Mental Shift — From React's Model to Svelte's
React’s mental model is built around immutability and explicit setters. You call useState, get a value and a setter, and you must call the setter every time you want an update. Svelte 5 flips this completely — assignment is reactivity. Write count++ and the DOM updates. No setter. No dispatch. No ceremony.
This is the single biggest shift when moving from React to Svelte.
The core idea: runes
Section titled “The core idea: runes”Svelte 5 introduces runes — special compiler-understood primitives that replace React hooks:
$statereplacesuseState— declares a reactive variable$derivedreplacesuseMemo— computes a value that updates when its dependencies change$effectreplacesuseEffect— runs a side effect whenever reactive values it reads change
Unlike React hooks, runes are not function calls with magic rules — they are compiler instructions. The Svelte compiler sees $state(0) and generates fine-grained DOM update code. No hook rules. No dependency arrays. No stale closure bugs.
Counter with derived state
Section titled “Counter with derived state”function Counter() { const [count, setCount] = React.useState(0); const doubled = count * 2; return ( <div> <button onClick={() => setCount(c => c + 1)}>+</button> <p>Count: {count}</p> <p>Doubled: {doubled}</p> </div> );}<script> let count = $state(0); let doubled = $derived(count * 2);</script>
<button onclick={() => count++}>+</button><p>Count: {count}</p><p>Doubled: {doubled}</p>Key differences:
- React needs
setCount— you cannot writecount++becausecountis a plainconst. Svelte lets you assign directly. - React’s
doubledis recalculated on every render. Svelte’s$derivedis tracked by the compiler — it only recalculates whencountchanges. - Svelte’s template is shorter because there is no wrapping function, no return, and no JSX closing tags.
Reactivity is the default
Section titled “Reactivity is the default”In React you opt in to reactivity: anything outside of useState or useReducer is not reactive. In Svelte you opt out: any $state variable is reactive everywhere it is read, including in the template, in $derived expressions, and inside $effect callbacks.
This means you can refactor freely. Extract a helper function that reads $state — it stays reactive. Pass a $state value to a child component — it stays reactive. The compiler tracks reads, not call sites.
$effect without dependency arrays
Section titled “$effect without dependency arrays”React’s useEffect requires a dependency array. Get it wrong and you have either a stale closure or an infinite loop. Svelte’s $effect auto-tracks every $state and $derived value read inside it — no array needed:
// React — you must list every dependencyuseEffect(() => { document.title = `Count: ${count}`;}, [count]);
// Svelte 5 — compiler tracks dependencies automatically$effect(() => { document.title = `Count: ${count}`;});If count changes, the effect re-runs. If you add another $state variable inside the effect later, it is tracked automatically. No missed dependencies, no lint warnings.