Skip to content

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.

Svelte 5 introduces runes — special compiler-understood primitives that replace React hooks:

  • $state replaces useState — declares a reactive variable
  • $derived replaces useMemo — computes a value that updates when its dependencies change
  • $effect replaces useEffect — 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.

React
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>
);
}
Svelte
<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 write count++ because count is a plain const. Svelte lets you assign directly.
  • React’s doubled is recalculated on every render. Svelte’s $derived is tracked by the compiler — it only recalculates when count changes.
  • Svelte’s template is shorter because there is no wrapping function, no return, and no JSX closing tags.

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.

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 dependency
useEffect(() => {
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.

How do you update reactive state in Svelte 5?
What is the Svelte 5 equivalent of React's useMemo?
What is the Svelte 5 equivalent of React's useEffect?
In Svelte 5, when you write `count++`, what happens?