Documentation Index
Fetch the complete documentation index at: https://mintlify.com/sveltejs/svelte/llms.txt
Use this file to discover all available pages before exploring further.
Migrating from React to Svelte offers significant benefits: smaller bundle sizes, better performance, and less boilerplate. This guide will help you understand the differences and successfully migrate your React applications to Svelte.
Why Migrate to Svelte?
- No Virtual DOM: Svelte compiles to efficient vanilla JavaScript
- Less Boilerplate: Write less code to achieve the same functionality
- Built-in Reactivity: No need for hooks or state management libraries
- Smaller Bundles: Significantly smaller production builds
- Better Performance: Faster runtime performance out of the box
Key Concept Mapping
| React Concept | Svelte Equivalent | Notes |
|---|
| JSX | Svelte Templates | No JSX transform needed |
useState | $state | Built-in reactivity |
useEffect | $effect | Simpler dependency tracking |
useMemo | $derived | Automatic memoization |
useCallback | Regular functions | No need to memoize |
useRef | let + bind:this | Simpler binding syntax |
useContext | Context API | Similar, but simpler |
useReducer | $state + functions | State management built-in |
| Props | $props() | Destructured props |
| Children | children prop | Rendered with {@render} |
| Custom Hooks | Runes | More powerful primitives |
Component Basics
Component Definition
import React from 'react';
function Welcome({ name }) {
return (
<div>
<h1>Hello, {name}!</h1>
</div>
);
}
export default Welcome;
<script>
let { name } = $props();
</script>
<div>
<h1>Hello, {name}!</h1>
</div>
State Management
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
<script>
let count = $state(0);
</script>
<button onclick={() => count++}>
Count: {count}
</button>
Computed Values
import { useState, useMemo } from 'react';
function ShoppingCart({ items }) {
const total = useMemo(() => {
return items.reduce((sum, item) => sum + item.price, 0);
}, [items]);
return <div>Total: ${total}</div>;
}
<script>
let { items } = $props();
let total = $derived(
items.reduce((sum, item) => sum + item.price, 0)
);
</script>
<div>Total: ${total}</div>
Effects and Side Effects
Basic Effects
import { useEffect, useState } from 'react';
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
return () => clearInterval(interval);
}, []);
return <div>Seconds: {seconds}</div>;
}
<script>
let seconds = $state(0);
$effect(() => {
const interval = setInterval(() => {
seconds++;
}, 1000);
return () => clearInterval(interval);
});
</script>
<div>Seconds: {seconds}</div>
Watching Dependencies
import { useEffect, useState } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(r => r.json())
.then(setUser);
}, [userId]);
return <div>{user?.name}</div>;
}
<script>
let { userId } = $props();
let user = $state(null);
$effect(() => {
fetch(`/api/users/${userId}`)
.then(r => r.json())
.then(data => user = data);
});
</script>
<div>{user?.name}</div>
Props and Component Communication
Props Passing
// Parent
function App() {
return (
<UserCard
name="Alice"
age={30}
email="alice@example.com"
/>
);
}
// Child
function UserCard({ name, age, email }) {
return (
<div>
<h2>{name}</h2>
<p>Age: {age}</p>
<p>Email: {email}</p>
</div>
);
}
<!-- Parent.svelte -->
<UserCard
name="Alice"
age={30}
email="alice@example.com"
/>
<!-- UserCard.svelte -->
<script>
let { name, age, email } = $props();
</script>
<div>
<h2>{name}</h2>
<p>Age: {age}</p>
<p>Email: {email}</p>
</div>
Default Props
function Button({ text = 'Click me', variant = 'primary' }) {
return <button className={variant}>{text}</button>;
}
<script>
let { text = 'Click me', variant = 'primary' } = $props();
</script>
<button class={variant}>{text}</button>
Two-Way Binding
// Parent
function App() {
const [value, setValue] = useState('');
return <Input value={value} onChange={setValue} />;
}
// Child
function Input({ value, onChange }) {
return (
<input
value={value}
onChange={(e) => onChange(e.target.value)}
/>
);
}
<!-- Parent.svelte -->
<script>
let value = $state('');
</script>
<Input bind:value />
<!-- Input.svelte -->
<script>
let { value = $bindable() } = $props();
</script>
<input bind:value />
Callbacks and Events
// Parent
function App() {
const handleClick = (data) => {
console.log('Clicked:', data);
};
return <CustomButton onClick={handleClick} />;
}
// Child
function CustomButton({ onClick }) {
return (
<button onClick={() => onClick({ id: 1 })}>
Click me
</button>
);
}
<!-- Parent.svelte -->
<script>
function handleClick(data) {
console.log('Clicked:', data);
}
</script>
<CustomButton onclick={handleClick} />
<!-- CustomButton.svelte -->
<script>
let { onclick } = $props();
</script>
<button onclick={() => onclick?.({ id: 1 })}>
Click me
</button>
Rendering Patterns
Conditional Rendering
function UserStatus({ isLoggedIn, user }) {
if (isLoggedIn) {
return <div>Welcome, {user.name}!</div>;
} else {
return <div>Please log in</div>;
}
}
// Or with ternary
function UserStatus({ isLoggedIn, user }) {
return (
<div>
{isLoggedIn ? (
<div>Welcome, {user.name}!</div>
) : (
<div>Please log in</div>
)}
</div>
);
}
<script>
let { isLoggedIn, user } = $props();
</script>
{#if isLoggedIn}
<div>Welcome, {user.name}!</div>
{:else}
<div>Please log in</div>
{/if}
List Rendering
function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
{todo.text}
</li>
))}
</ul>
);
}
<script>
let { todos } = $props();
</script>
<ul>
{#each todos as todo (todo.id)}
<li>{todo.text}</li>
{/each}
</ul>
Children and Composition
// Container component
function Card({ children, title }) {
return (
<div className="card">
<h2>{title}</h2>
{children}
</div>
);
}
// Usage
function App() {
return (
<Card title="My Card">
<p>Card content goes here</p>
</Card>
);
}
<!-- Card.svelte -->
<script>
let { children, title } = $props();
</script>
<div class="card">
<h2>{title}</h2>
{@render children()}
</div>
<!-- App.svelte -->
<Card title="My Card">
<p>Card content goes here</p>
</Card>
function Form() {
const [formData, setFormData] = useState({
name: '',
email: '',
message: ''
});
const handleChange = (field) => (e) => {
setFormData(prev => ({
...prev,
[field]: e.target.value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
console.log(formData);
};
return (
<form onSubmit={handleSubmit}>
<input
value={formData.name}
onChange={handleChange('name')}
/>
<input
value={formData.email}
onChange={handleChange('email')}
/>
<textarea
value={formData.message}
onChange={handleChange('message')}
/>
<button type="submit">Submit</button>
</form>
);
}
<script>
let formData = $state({
name: '',
email: '',
message: ''
});
function handleSubmit(e) {
e.preventDefault();
console.log(formData);
}
</script>
<form onsubmit={handleSubmit}>
<input bind:value={formData.name} />
<input bind:value={formData.email} />
<textarea bind:value={formData.message} />
<button type="submit">Submit</button>
</form>
Lifecycle and Refs
Component Lifecycle
import { useEffect } from 'react';
function Component() {
useEffect(() => {
console.log('Component mounted');
return () => {
console.log('Component unmounting');
};
}, []);
return <div>Component</div>;
}
<script>
import { onMount } from 'svelte';
onMount(() => {
console.log('Component mounted');
return () => {
console.log('Component unmounting');
};
});
</script>
<div>Component</div>
DOM Refs
import { useRef, useEffect } from 'react';
function AutoFocusInput() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current?.focus();
}, []);
return <input ref={inputRef} />;
}
<script>
import { onMount } from 'svelte';
let inputElement;
onMount(() => {
inputElement?.focus();
});
</script>
<input bind:this={inputElement} />
Context API
import { createContext, useContext } from 'react';
const ThemeContext = createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<ThemedComponent />
</ThemeContext.Provider>
);
}
function ThemedComponent() {
const theme = useContext(ThemeContext);
return <div>Theme: {theme}</div>;
}
<!-- App.svelte -->
<script>
import { setContext } from 'svelte';
import ThemedComponent from './ThemedComponent.svelte';
setContext('theme', 'dark');
</script>
<ThemedComponent />
<!-- ThemedComponent.svelte -->
<script>
import { getContext } from 'svelte';
const theme = getContext('theme');
</script>
<div>Theme: {theme}</div>
Advanced Patterns
Higher-Order Components vs Composition
// HOC pattern
function withAuth(Component) {
return function AuthenticatedComponent(props) {
const { isAuthenticated } = useAuth();
if (!isAuthenticated) {
return <Login />;
}
return <Component {...props} />;
};
}
const ProtectedPage = withAuth(Dashboard);
<!-- AuthGuard.svelte -->
<script>
import { getContext } from 'svelte';
import Login from './Login.svelte';
let { children } = $props();
const isAuthenticated = getContext('auth');
</script>
{#if isAuthenticated}
{@render children()}
{:else}
<Login />
{/if}
<!-- Usage -->
<AuthGuard>
<Dashboard />
</AuthGuard>
Render Props Pattern
function DataFetcher({ url, render }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(url)
.then(r => r.json())
.then(data => {
setData(data);
setLoading(false);
});
}, [url]);
return render({ data, loading });
}
// Usage
<DataFetcher
url="/api/users"
render={({ data, loading }) => (
loading ? <div>Loading...</div> : <div>{data}</div>
)}
/>
<!-- DataFetcher.svelte -->
<script>
let { url, children } = $props();
let data = $state(null);
let loading = $state(true);
$effect(() => {
loading = true;
fetch(url)
.then(r => r.json())
.then(result => {
data = result;
loading = false;
});
});
</script>
{@render children({ data, loading })}
<!-- Usage -->
<DataFetcher url="/api/users">
{#snippet children({ data, loading })}
{#if loading}
<div>Loading...</div>
{:else}
<div>{data}</div>
{/if}
{/snippet}
</DataFetcher>
State Management
Local State
import { useState } from 'react';
function ShoppingCart() {
const [items, setItems] = useState([]);
const addItem = (item) => {
setItems(prev => [...prev, item]);
};
const removeItem = (id) => {
setItems(prev => prev.filter(item => item.id !== id));
};
return (
<div>
{items.map(item => (
<div key={item.id}>
{item.name}
<button onClick={() => removeItem(item.id)}>Remove</button>
</div>
))}
</div>
);
}
<script>
let items = $state([]);
function addItem(item) {
items.push(item); // Direct mutation works!
}
function removeItem(id) {
items = items.filter(item => item.id !== id);
}
</script>
<div>
{#each items as item (item.id)}
<div>
{item.name}
<button onclick={() => removeItem(item.id)}>Remove</button>
</div>
{/each}
</div>
Global State (Without External Libraries)
import { createContext, useContext, useState } from 'react';
const CartContext = createContext();
export function CartProvider({ children }) {
const [cart, setCart] = useState([]);
const addToCart = (item) => {
setCart(prev => [...prev, item]);
};
return (
<CartContext.Provider value={{ cart, addToCart }}>
{children}
</CartContext.Provider>
);
}
export function useCart() {
return useContext(CartContext);
}
// stores.js
import { writable } from 'svelte/store';
function createCart() {
const { subscribe, update } = writable([]);
return {
subscribe,
addToCart: (item) => update(items => [...items, item])
};
}
export const cart = createCart();
// Component.svelte
<script>
import { cart } from './stores.js';
</script>
<div>Items: {$cart.length}</div>
<button onclick={() => cart.addToCart({ id: 1 })}>Add</button>
Styling
Component Styles
import styles from './Button.module.css';
function Button({ children }) {
return (
<button className={styles.button}>
{children}
</button>
);
}
<script>
let { children } = $props();
</script>
<button>
{@render children()}
</button>
<style>
button {
/* Scoped to this component by default */
padding: 10px 20px;
background: blue;
color: white;
}
</style>
Dynamic Styles
function Box({ color, size }) {
return (
<div style={{
backgroundColor: color,
width: `${size}px`,
height: `${size}px`
}}>
Box
</div>
);
}
<script>
let { color, size } = $props();
</script>
<div style:background-color={color} style:width="{size}px" style:height="{size}px">
Box
</div>
Migration Checklist
Key Differences to Remember
- No Virtual DOM: Svelte compiles to vanilla JavaScript
- Built-in Reactivity: No need for
setState or hooks
- Scoped Styles: CSS is scoped by default
- Less Boilerplate: Write less code for the same functionality
- True Reactivity: Assignments trigger updates automatically
- No Re-renders: Only changed parts of the DOM update
Next Steps