$effect — Side Effects
useEffect has a well-known footgun: forget a variable in the dependency array and your effect runs with a stale value. Add too many and it runs too often. Svelte 5’s $effect solves this completely — there is no dependency array. The effect runs once, then re-runs automatically every time any reactive value it read during the previous run changes. The compiler tracks dependencies; you don’t.
Basic side effect
Section titled “Basic side effect”function TitleSync() { const [count, setCount] = React.useState(0);
React.useEffect(() => { document.title = 'Count: ' + count; }, [count]); // <-- must list count here
return <button onClick={() => setCount(c => c + 1)}>+1 (count: {count})</button>;}<script> let count = $state(0);
$effect(() => { document.title = 'Count: ' + count; // count is read here, so it is tracked automatically });</script>
<button onclick={() => count++}>+1 (count: {count})</button>No [count]. The effect reads count, so Svelte knows to re-run it whenever count changes.
When does $effect run?
Section titled “When does $effect run?”- First run: after the component mounts (same as
useEffectwith no deps). - Re-runs: synchronously after any reactive dependency it read on the previous run changes.
- Not during render: unlike
useMemo,$effectnever runs during the synchronous rendering phase.
Cleanup
Section titled “Cleanup”Return a function from $effect to clean up before the next run or when the component is destroyed — identical to useEffect’s cleanup pattern.
function Timer() { const [seconds, setSeconds] = React.useState(0);
React.useEffect(() => { const id = setInterval(() => setSeconds(s => s + 1), 1000); return () => clearInterval(id); // cleanup }, []);
return <p>Elapsed: {seconds}s</p>;}<script> import { onMount } from 'svelte'; let seconds = $state(0);
onMount(() => { const id = setInterval(() => seconds++, 1000); return () => clearInterval(id); // cleanup });</script>
<p>Elapsed: {seconds}s</p>For a “run once on mount + cleanup on destroy” pattern, onMount is cleaner than $effect. More on that in the Lifecycle lesson.
$effect for localStorage sync
Section titled “$effect for localStorage sync”A common useEffect use case — syncing state to localStorage — maps directly:
function ThemeToggle() { const [theme, setTheme] = React.useState( () => localStorage.getItem('theme') ?? 'light' );
React.useEffect(() => { localStorage.setItem('theme', theme); document.body.setAttribute('data-theme', theme); }, [theme]);
return ( <button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}> Theme: {theme} </button> );}<script> let theme = $state( typeof localStorage !== 'undefined' ? (localStorage.getItem('theme') ?? 'light') : 'light' );
$effect(() => { localStorage.setItem('theme', theme); document.body.setAttribute('data-theme', theme); });</script>
<button onclick={() => theme = theme === 'light' ? 'dark' : 'light'}> Theme: {theme}</button>