Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ee9346a
WIP: Svelte 5 adapter (#6981)
zhihengGet Jul 26, 2024
b5f5fd4
feat(svelte-query): Improve svelte runes API (#8852)
elliott-with-the-longest-name-on-github May 12, 2025
594e0bf
Merge branch 'main' into svelte-5-adapter
lachlancollins May 12, 2025
e3f7484
Use sleep from query-test-utils
lachlancollins May 12, 2025
859d9b7
Simplify test reset logic
lachlancollins May 12, 2025
746b9c5
Merge branch 'main' into svelte-5-adapter
lachlancollins May 12, 2025
6e65a4a
Merge branch 'main' into svelte-5-adapter
lachlancollins Sep 26, 2025
540ce06
Fix some merge conflicts
lachlancollins Sep 26, 2025
f0a9ebe
More fixes
lachlancollins Sep 26, 2025
cc5a571
A few more fixes
lachlancollins Sep 26, 2025
d484552
Merge branch 'main' into svelte-5-adapter
lachlancollins Sep 26, 2025
686db47
Fix useMutationState
lachlancollins Sep 26, 2025
e3bba4a
Add changeset
lachlancollins Sep 26, 2025
373e24b
Add migration docs
lachlancollins Sep 26, 2025
d0beb44
Merge branch 'main' into svelte-5-adapter
lachlancollins Sep 27, 2025
bd8737f
Replace Set with SvelteSet
lachlancollins Sep 27, 2025
e07e895
Update minimum svelte version
lachlancollins Sep 27, 2025
9f5c7e8
Bump svelte-eslint-parser
lachlancollins Sep 27, 2025
eea078e
Unwrap createQuery test
lachlancollins Sep 27, 2025
968d6a8
fix(svelte-query): `state_unsafe_mutation` error with `useIs...` (#9493)
hmnd Sep 27, 2025
62be7d5
chore(svelte-query): fix eslint config (#9699)
lachlancollins Sep 27, 2025
c1e67ed
Merge branch 'main' into svelte-5-adapter
lachlancollins Sep 27, 2025
c6d0b92
Merge branch 'main' into svelte-5-adapter
lachlancollins Sep 30, 2025
05f0acd
ci: apply automated fixes
autofix-ci[bot] Sep 30, 2025
7049e4b
Fix sherif
lachlancollins Sep 30, 2025
0c44073
Update docs and changeset
lachlancollins Sep 30, 2025
f3fc6a2
Update keywords
lachlancollins Sep 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/pink-pots-jam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@tanstack/svelte-query-persist-client': major
'@tanstack/svelte-query-devtools': major
'@tanstack/svelte-query': major
---

BREAKING: Migrate to svelte runes (svelte v5+). Please see documentation for migration guide.
4 changes: 0 additions & 4 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,6 @@
{
"label": "SSR & SvelteKit",
"to": "framework/svelte/ssr"
},
{
"label": "Reactivity",
"to": "framework/svelte/reactivity"
}
]
},
Expand Down
2 changes: 0 additions & 2 deletions docs/framework/svelte/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ title: Installation

You can install Svelte Query via [NPM](https://npmjs.com).

> v5 is currently available as a release-candidate. We don't anticipate any major API changes from here on out. We encourage you to try it out and report any issues you find.
### NPM

```bash
Expand Down
19 changes: 10 additions & 9 deletions docs/framework/svelte/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,19 @@ Then call any function (e.g. createQuery) from any component:
<script lang="ts">
import { createQuery } from '@tanstack/svelte-query'

const query = createQuery({
const query = createQuery(() => ({
queryKey: ['todos'],
queryFn: () => fetchTodos(),
})
}))
</script>

<div>
{#if $query.isLoading}
{#if query.isLoading}
<p>Loading...</p>
{:else if $query.isError}
<p>Error: {$query.error.message}</p>
{:else if $query.isSuccess}
{#each $query.data as todo}
{:else if query.isError}
<p>Error: {query.error.message}</p>
{:else if query.isSuccess}
{#each query.data as todo}
<p>{todo.title}</p>
{/each}
{/if}
Expand All @@ -62,6 +62,8 @@ Svelte Query offers useful functions and components that will make managing serv
- `useQueryClient`
- `useIsFetching`
- `useIsMutating`
- `useMutationState`
- `useIsRestoring`
- `useHydrate`
- `<QueryClientProvider>`
- `<HydrationBoundary>`
Expand All @@ -70,5 +72,4 @@ Svelte Query offers useful functions and components that will make managing serv

Svelte Query offers an API similar to React Query, but there are some key differences to be mindful of.

- Many of the functions in Svelte Query return a Svelte store. To access values on these stores reactively, you need to prefix the store with a `$`. You can learn more about Svelte stores [here](https://learn.svelte.dev/tutorial/writable-stores).
- If your query or mutation depends on variables, you must use a store for the options. You can read more about this [here](../reactivity).
- The arguments to the `create*` functions must be wrapped in a function to preserve reactivity.
45 changes: 0 additions & 45 deletions docs/framework/svelte/reactivity.md

This file was deleted.

8 changes: 4 additions & 4 deletions docs/framework/svelte/ssr.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@ export async function load() {

export let data: PageData

const query = createQuery({
const query = createQuery(() => ({
queryKey: ['posts'],
queryFn: getPosts,
initialData: data.posts,
})
}))
</script>
```

Expand Down Expand Up @@ -136,10 +136,10 @@ export async function load({ parent, fetch }) {
import { createQuery } from '@tanstack/svelte-query'

// This data is cached by prefetchQuery in +page.ts so no fetch actually happens here
const query = createQuery({
const query = createQuery(() => ({
queryKey: ['posts'],
queryFn: async () => (await fetch('/api/posts')).json(),
})
}))
</script>
```

Expand Down
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export default [
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-unsafe-function-type': 'off',
'no-case-declarations': 'off',
'prefer-const': 'off',
},
},
{
Expand Down
4 changes: 3 additions & 1 deletion examples/svelte/auto-refetching/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import { QueryClientProvider, QueryClient } from '@tanstack/svelte-query'
import { SvelteQueryDevtools } from '@tanstack/svelte-query-devtools'
const { children } = $props()
const queryClient = new QueryClient({
defaultOptions: {
queries: {
Expand All @@ -15,7 +17,7 @@

<QueryClientProvider client={queryClient}>
<main>
<slot />
{@render children()}
</main>
<SvelteQueryDevtools />
</QueryClientProvider>
56 changes: 31 additions & 25 deletions examples/svelte/auto-refetching/src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,32 @@
createMutation,
} from '@tanstack/svelte-query'
let intervalMs = 1000
let value = ''
let intervalMs = $state(1000)
let value = $state<string>('')
const client = useQueryClient()
const endpoint = '/api/data'
$: todos = createQuery<{ items: string[] }>({
const todos = createQuery<{ items: string[] }>(() => ({
queryKey: ['refetch'],
queryFn: async () => await fetch(endpoint).then((r) => r.json()),
// Refetch the data every second
refetchInterval: intervalMs,
})
}))
const addMutation = createMutation({
const addMutation = createMutation(() => ({
mutationFn: (value: string) =>
fetch(`${endpoint}?add=${encodeURIComponent(value)}`).then((r) =>
r.json(),
),
onSuccess: () => client.invalidateQueries({ queryKey: ['refetch'] }),
})
}))
const clearMutation = createMutation({
const clearMutation = createMutation(() => ({
mutationFn: () => fetch(`${endpoint}?clear=1`).then((r) => r.json()),
onSuccess: () => client.invalidateQueries({ queryKey: ['refetch'] }),
})
}))
</script>

<h1>Auto Refetch with stale-time set to {intervalMs}ms</h1>
Expand All @@ -51,53 +51,59 @@
margin-left:.5rem;
width:.75rem;
height:.75rem;
background: {$todos.isFetching ? 'green' : 'transparent'};
transition: {!$todos.isFetching ? 'all .3s ease' : 'none'};
background: {todos.isFetching ? 'green' : 'transparent'};
transition: {!todos.isFetching ? 'all .3s ease' : 'none'};
border-radius: 100%;
transform: scale(1.5)"
></span>
</div>
</label>
<h2>Todo List</h2>
<form
on:submit={(e) => {
onsubmit={(e) => {
e.preventDefault()
e.stopPropagation()
$addMutation.mutate(value, {
addMutation.mutate(value, {
onSuccess: () => (value = ''),
})
}}
>
<input placeholder="enter something" bind:value />
</form>

{#if $todos.isPending}
{#if todos.isPending}
Loading...
{/if}
{#if $todos.error}
{#if todos.error}
An error has occurred:
{$todos.error.message}
{todos.error.message}
{/if}
{#if $todos.isSuccess}
{#if todos.isSuccess}
<ul>
{#each $todos.data.items as item}
{#each todos.data.items as item}
<li>{item}</li>
{/each}
</ul>
<div>
<button on:click={() => $clearMutation.mutate(undefined)}>
Clear All
</button>
</div>
{/if}
{#if $todos.isFetching}
<div style="color:darkgreen; font-weight:700">
'Background Updating...' : ' '
<button onclick={() => clearMutation.mutate(undefined)}> Clear All </button>
</div>
{/if}

<pre
class={['updating-text', todos.isFetching && 'on']}
style="font-weight:700">Background Updating...</pre>

<style>
li {
text-align: left;
}
.updating-text {
color: transparent;
transition: all 0.3s ease;
}
.updating-text.on {
color: green;
transition: none;
}
</style>
3 changes: 3 additions & 0 deletions examples/svelte/auto-refetching/svelte.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ const config = {
kit: {
adapter: adapter(),
},
compilerOptions: {
runes: true,
},
}

export default config
20 changes: 10 additions & 10 deletions examples/svelte/basic/src/lib/Post.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,29 @@
import { getPostById } from './data'
import type { Post } from './types'
export let postId: number
const { postId }: { postId: number } = $props()
const post = createQuery<Post>({
const post = createQuery<Post>(() => ({
queryKey: ['post', postId],
queryFn: () => getPostById(postId),
})
}))
Comment on lines +8 to +11
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard query execution when postId is falsy.

Without enabled, queryFn may run with undefined postId.

Apply:

-  const post = createQuery<Post>(() => ({
+  const post = createQuery<Post>(() => ({
     queryKey: ['post', postId],
     queryFn: () => getPostById(postId),
+    enabled: !!postId,
   }))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const post = createQuery<Post>(() => ({
queryKey: ['post', postId],
queryFn: () => getPostById(postId),
})
}))
const post = createQuery<Post>(() => ({
queryKey: ['post', postId],
queryFn: () => getPostById(postId),
enabled: !!postId,
}))
🤖 Prompt for AI Agents
In examples/svelte/basic/src/lib/Post.svelte around lines 8 to 11, the query is
created without guarding against a falsy postId so queryFn may run with
undefined; add an enabled flag such as enabled: Boolean(postId) (or !!postId) to
the createQuery options so the query will not execute until postId is truthy,
and keep the existing queryKey/queryFn but ensure getPostById is only called
when enabled.

</script>

<div>
<div>
<a class="button" href="/"> Back </a>
</div>
{#if !postId || $post.isPending}
{#if !postId || post.isPending}
<span>Loading...</span>
{/if}
{#if $post.error}
<span>Error: {$post.error.message}</span>
{#if post.error}
<span>Error: {post.error.message}</span>
{/if}
{#if $post.isSuccess}
<h1>{$post.data.title}</h1>
{#if post.isSuccess}
<h1>{post.data.title}</h1>
<div>
<p>{$post.data.body}</p>
<p>{post.data.body}</p>
</div>
<div>{$post.isFetching ? 'Background Updating...' : ' '}</div>
<div>{post.isFetching ? 'Background Updating...' : ' '}</div>
{/if}
</div>
30 changes: 18 additions & 12 deletions examples/svelte/basic/src/lib/Posts.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,21 @@
const posts = createQuery<
{ id: number; title: string; body: string }[],
Error
>({
>(() => ({
queryKey: ['posts', limit],
queryFn: () => getPosts(limit),
})
}))
</script>

<div>
<div>
{#if $posts.status === 'pending'}
{#if posts.status === 'pending'}
<span>Loading...</span>
{:else if $posts.status === 'error'}
<span>Error: {$posts.error.message}</span>
{:else if posts.status === 'error'}
<span>Error: {posts.error.message}</span>
{:else}
<ul>
{#each $posts.data as post}
{#each posts.data as post}
<article>
<a
href={`/${post.id}`}
Expand All @@ -38,11 +38,9 @@
</article>
{/each}
</ul>
{#if $posts.isFetching}
<div style="color:darkgreen; font-weight:700">
Background Updating...
</div>
{/if}
<pre
class={['updating-text', posts.isFetching && 'on']}
style="font-weight:700">Background Updating...</pre>
{/if}
</div>
</div>
Expand All @@ -53,8 +51,16 @@
}
a {
display: block;
color: white;
font-size: 1.5rem;
margin-bottom: 1rem;
}
.updating-text {
color: transparent;
transition: all 0.3s ease;
}
.updating-text.on {
color: green;
transition: none;
}
</style>
Loading
Loading