Load and mutate data in Svelte — render promises with {#await}, re-fetch when inputs change with $derived, and post writes with pending state.
Why: Svelte renders promises directly in the markup — {#await} shows one branch while loading, one when the data arrives, and one if it fails. No loading flags, no effect, no library. Note: in a SvelteKit app, page data usually comes from load functions instead — that's covered in the SvelteKit course.
<script lang="ts">
type User = { id: number; name: string }
async function getUsers(): Promise<User[]> {
const res = await fetch('https://jsonplaceholder.typicode.com/users')
if (!res.ok) throw new Error('Request failed')
return res.json()
}
const users = getUsers() // a promise — no await in the script
</script>
{#await users}
<p>Loading…</p>
{:then list}
<ul>
{#each list as user (user.id)}
<li>{user.name}</li>
{/each}
</ul>
{:catch error}
<p>Error: {error.message}</p>
{/await}Why: search-as-you-type and filters need a NEW request whenever the input changes. Make the promise itself a $derived — every change to the input creates a fresh promise, and {#await} picks it up automatically.
<script lang="ts">
type Repo = { full_name: string; stargazers_count: number }
let org = $state('sveltejs')
async function getRepos(name: string): Promise<Repo[]> {
const res = await fetch(`https://api.github.com/orgs/${name}/repos`)
if (!res.ok) throw new Error('Request failed')
return res.json()
}
// A new promise every time org changes — {#await} follows along
let repos = $derived(getRepos(org))
</script>
<input bind:value={org} placeholder="GitHub org" />
{#await repos}
<p>Loading…</p>
{:then list}
<p>{list.length} repos</p>
{:catch error}
<p>Error: {error.message}</p>
{/await}Why: writes (POST, PUT, DELETE) need two things reads don't — a pending flag so the user can't double-submit, and a refetch afterwards so the screen matches the server. A small async function with a $state flag covers both.
<script lang="ts">
type Comment = { id: number; text: string }
async function getComments(): Promise<Comment[]> {
return (await fetch('/api/comments')).json()
}
let comments = $state(getComments())
let pending = $state(false)
async function addComment() {
pending = true
// POST the write…
await fetch('/api/comments', {
method: 'POST',
body: JSON.stringify({ text: 'Great post!' }),
})
// …then refresh the read so the list matches the server
comments = getComments()
pending = false
}
</script>
{#await comments then list}
<p>{list.length} comments</p>
{/await}
<button disabled={pending} onclick={addComment}>
{pending ? 'Sending…' : 'Add comment'}
</button>