Skip to content

Commit 1056ba6

Browse files
lachlancollinszhihengGetelliott-with-the-longest-name-on-githubautofix-ci[bot]hmnd
authored
feat!(svelte-query): rewrite Svelte adapter to use runes (#9694)
* WIP: Svelte 5 adapter (#6981) * feat(svelte-query): Improve svelte runes API (#8852) * feat: Draft proposal * chore: Improve reactive containers * ci: apply automated fixes * oops * fix: Update API, add a bunch of tests * merge main * fix: use const * more tests * feat: More tests, back to thunks, fixed svelte-query-persist-client * feat: More tests and examples! * lockfile * fixes * Fix current CI errors * More small fixes/tweaks * Remove test.only * ci: apply automated fixes * Fix pnpm-lock, fix import order * update main docs * feat: More tests * ci: apply automated fixes * add back old tests * Cleanup * Fix persist client * Fix useMutationState --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Lachlan Collins <[email protected]> * Use sleep from query-test-utils * Simplify test reset logic * Fix some merge conflicts * More fixes * A few more fixes * Fix useMutationState * Add changeset * Add migration docs * Replace Set with SvelteSet * Update minimum svelte version * Bump svelte-eslint-parser * Unwrap createQuery test * fix(svelte-query): `state_unsafe_mutation` error with `useIs...` (#9493) * fix(svelte-query): don't wrap observers in derived to avoid state_unsafe_mutation fixes useIsFetching and useIsMutating in svelte 5 adapter * test(svelte-query): wrap (useIs...) tests in QueryClientProvider to test non colocated query * fix(svelte-query): update observers when passed in query client changes * fix(svelte-query): simplify creatMutation sub/unsub * Refactor result handling in createMutation.svelte.ts Replace derived state with direct state and add watchChanges for result updates. --------- Co-authored-by: Lachlan Collins <[email protected]> * chore(svelte-query): fix eslint config (#9699) * chore(svelte-query): fix eslint config * Use @typescript-eslint/parser directly * ci: apply automated fixes * Fix sherif * Update docs and changeset * Update keywords --------- Co-authored-by: Zhiheng Zhang <[email protected]> Co-authored-by: Elliott Johnson <[email protected]> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: David <[email protected]>
1 parent a242f98 commit 1056ba6

File tree

148 files changed

+4860
-2018
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

148 files changed

+4860
-2018
lines changed

.changeset/pink-pots-jam.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@tanstack/svelte-query-persist-client': major
3+
'@tanstack/svelte-query-devtools': major
4+
'@tanstack/svelte-query': major
5+
---
6+
7+
BREAKING: Migrate to svelte runes (signals). Requires [Svelte v5.25.0](https://github.com/sveltejs/svelte/releases/tag/svelte%405.25.0) or newer. Please see the [migration guide](https://tanstack.com/query/latest/docs/framework/svelte/migrate-from-v5-to-v6).

docs/config.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,8 @@
129129
"to": "framework/svelte/ssr"
130130
},
131131
{
132-
"label": "Reactivity",
133-
"to": "framework/svelte/reactivity"
132+
"label": "Migrate from v5 to v6",
133+
"to": "framework/svelte/migrate-from-v5-to-v6"
134134
}
135135
]
136136
},

docs/framework/svelte/installation.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ title: Installation
55

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

8-
> 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.
9-
108
### NPM
119

1210
```bash
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
## Overview
2+
3+
While Svelte v5 has legacy compatibility with the stores syntax from Svelte v3/v4, it has been somewhat buggy and unreliable for this adapter. The `@tanstack/svelte-query` v6 adapter fully migrates to the runes syntax, which relies on signals. This rewrite should also simplify the code required to ensure your query inputs remain reactive.
4+
5+
## Installation
6+
7+
Please ensure your project has [Svelte v5.25.0](https://github.com/sveltejs/svelte/releases/tag/svelte%405.25.0) or newer.
8+
9+
Run `pnpm add @tanstack/svelte-query@latest` (or your package manager's equivalent).
10+
11+
> Note that `@tanstack/svelte-query` v6 depends on `@tanstack/query-core` v5.
12+
13+
## Thunks
14+
15+
Like the Solid adapter, most functions for the Svelte adapter now require options to be provided as a "thunk" (`() => options`) to provide reactivity.
16+
17+
```diff
18+
-const query = createQuery({
19+
+const query = createQuery(() => ({
20+
queryKey: ['todos'],
21+
queryFn: () => fetchTodos(),
22+
-})
23+
+}))
24+
```
25+
26+
## Accessing Properties
27+
28+
Given the adapter no longer uses stores, it is no longer necessary to prefix with `$`.
29+
30+
```diff
31+
-{#if $todos.isSuccess}
32+
+{#if todos.isSuccess}
33+
<ul>
34+
- {#each $todos.data.items as item}
35+
+ {#each todos.data.items as item}
36+
<li>{item}</li>
37+
{/each}
38+
</ul>
39+
{/if}
40+
```
41+
42+
## Reactivity
43+
44+
You previously needed to do some funky things with stores to achieve reactivity for inputs. This is no longer the case! You don't even need to wrap your query in a `$derived`.
45+
46+
```diff
47+
-const intervalMs = writable(1000)
48+
+let intervalMs = $state(1000)
49+
50+
-const query = createQuery(derived(intervalMs, ($intervalMs) => ({
51+
+const query = createQuery(() => ({
52+
queryKey: ['refetch'],
53+
queryFn: async () => await fetch('/api/data').then((r) => r.json()),
54+
refetchInterval: $intervalMs,
55+
-})))
56+
+}))
57+
```
58+
59+
## Disabling Legacy Mode
60+
61+
If your component has any stores, it might not properly switch to runes mode. You can ensure your application is using runes in two ways:
62+
63+
### On a per-file basis
64+
65+
In each `.svelte` file, once you have migrated to runes, add `<svelte:options runes={true} />`. This is better for large applications requiring gradual migration.
66+
67+
### On an project-wide basis
68+
69+
In your `svelte.config.js`, add the following to config:
70+
71+
```json
72+
compilerOptions: {
73+
runes: true,
74+
},
75+
```
76+
77+
This can be added once you've 100% eradicated stores syntax from your app.

docs/framework/svelte/overview.md

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ title: Overview
55

66
The `@tanstack/svelte-query` package offers a 1st-class API for using TanStack Query via Svelte.
77

8+
> Migrating from stores to the runes syntax? See the [migration guide](../migrate-from-v5-to-v6).
9+
810
## Example
911

1012
Include the QueryClientProvider near the root of your project:
@@ -28,19 +30,19 @@ Then call any function (e.g. createQuery) from any component:
2830
<script lang="ts">
2931
import { createQuery } from '@tanstack/svelte-query'
3032
31-
const query = createQuery({
33+
const query = createQuery(() => ({
3234
queryKey: ['todos'],
3335
queryFn: () => fetchTodos(),
34-
})
36+
}))
3537
</script>
3638
3739
<div>
38-
{#if $query.isLoading}
40+
{#if query.isLoading}
3941
<p>Loading...</p>
40-
{:else if $query.isError}
41-
<p>Error: {$query.error.message}</p>
42-
{:else if $query.isSuccess}
43-
{#each $query.data as todo}
42+
{:else if query.isError}
43+
<p>Error: {query.error.message}</p>
44+
{:else if query.isSuccess}
45+
{#each query.data as todo}
4446
<p>{todo.title}</p>
4547
{/each}
4648
{/if}
@@ -62,6 +64,8 @@ Svelte Query offers useful functions and components that will make managing serv
6264
- `useQueryClient`
6365
- `useIsFetching`
6466
- `useIsMutating`
67+
- `useMutationState`
68+
- `useIsRestoring`
6569
- `useHydrate`
6670
- `<QueryClientProvider>`
6771
- `<HydrationBoundary>`
@@ -70,5 +74,4 @@ Svelte Query offers useful functions and components that will make managing serv
7074

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

73-
- 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).
74-
- If your query or mutation depends on variables, you must use a store for the options. You can read more about this [here](../reactivity).
77+
- The arguments to the `create*` functions must be wrapped in a function to preserve reactivity.

docs/framework/svelte/reactivity.md

Lines changed: 0 additions & 45 deletions
This file was deleted.

docs/framework/svelte/ssr.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@ export async function load() {
5858
5959
export let data: PageData
6060
61-
const query = createQuery({
61+
const query = createQuery(() => ({
6262
queryKey: ['posts'],
6363
queryFn: getPosts,
6464
initialData: data.posts,
65-
})
65+
}))
6666
</script>
6767
```
6868

@@ -136,10 +136,10 @@ export async function load({ parent, fetch }) {
136136
import { createQuery } from '@tanstack/svelte-query'
137137
138138
// This data is cached by prefetchQuery in +page.ts so no fetch actually happens here
139-
const query = createQuery({
139+
const query = createQuery(() => ({
140140
queryKey: ['posts'],
141141
queryFn: async () => (await fetch('/api/posts')).json(),
142-
})
142+
}))
143143
</script>
144144
```
145145

eslint.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export default [
4444
'@typescript-eslint/no-empty-function': 'off',
4545
'@typescript-eslint/no-unsafe-function-type': 'off',
4646
'no-case-declarations': 'off',
47+
'prefer-const': 'off',
4748
},
4849
},
4950
{

examples/svelte/auto-refetching/src/routes/+layout.svelte

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import { QueryClientProvider, QueryClient } from '@tanstack/svelte-query'
55
import { SvelteQueryDevtools } from '@tanstack/svelte-query-devtools'
66
7+
const { children } = $props()
8+
79
const queryClient = new QueryClient({
810
defaultOptions: {
911
queries: {
@@ -15,7 +17,7 @@
1517

1618
<QueryClientProvider client={queryClient}>
1719
<main>
18-
<slot />
20+
{@render children()}
1921
</main>
2022
<SvelteQueryDevtools />
2123
</QueryClientProvider>

examples/svelte/auto-refetching/src/routes/+page.svelte

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,32 @@
55
createMutation,
66
} from '@tanstack/svelte-query'
77
8-
let intervalMs = 1000
9-
let value = ''
8+
let intervalMs = $state(1000)
9+
let value = $state<string>('')
1010
1111
const client = useQueryClient()
1212
1313
const endpoint = '/api/data'
1414
15-
$: todos = createQuery<{ items: string[] }>({
15+
const todos = createQuery<{ items: string[] }>(() => ({
1616
queryKey: ['refetch'],
1717
queryFn: async () => await fetch(endpoint).then((r) => r.json()),
1818
// Refetch the data every second
1919
refetchInterval: intervalMs,
20-
})
20+
}))
2121
22-
const addMutation = createMutation({
22+
const addMutation = createMutation(() => ({
2323
mutationFn: (value: string) =>
2424
fetch(`${endpoint}?add=${encodeURIComponent(value)}`).then((r) =>
2525
r.json(),
2626
),
2727
onSuccess: () => client.invalidateQueries({ queryKey: ['refetch'] }),
28-
})
28+
}))
2929
30-
const clearMutation = createMutation({
30+
const clearMutation = createMutation(() => ({
3131
mutationFn: () => fetch(`${endpoint}?clear=1`).then((r) => r.json()),
3232
onSuccess: () => client.invalidateQueries({ queryKey: ['refetch'] }),
33-
})
33+
}))
3434
</script>
3535

3636
<h1>Auto Refetch with stale-time set to {intervalMs}ms</h1>
@@ -51,53 +51,59 @@
5151
margin-left:.5rem;
5252
width:.75rem;
5353
height:.75rem;
54-
background: {$todos.isFetching ? 'green' : 'transparent'};
55-
transition: {!$todos.isFetching ? 'all .3s ease' : 'none'};
54+
background: {todos.isFetching ? 'green' : 'transparent'};
55+
transition: {!todos.isFetching ? 'all .3s ease' : 'none'};
5656
border-radius: 100%;
5757
transform: scale(1.5)"
5858
></span>
5959
</div>
6060
</label>
6161
<h2>Todo List</h2>
6262
<form
63-
on:submit={(e) => {
63+
onsubmit={(e) => {
6464
e.preventDefault()
6565
e.stopPropagation()
66-
$addMutation.mutate(value, {
66+
addMutation.mutate(value, {
6767
onSuccess: () => (value = ''),
6868
})
6969
}}
7070
>
7171
<input placeholder="enter something" bind:value />
7272
</form>
7373

74-
{#if $todos.isPending}
74+
{#if todos.isPending}
7575
Loading...
7676
{/if}
77-
{#if $todos.error}
77+
{#if todos.error}
7878
An error has occurred:
79-
{$todos.error.message}
79+
{todos.error.message}
8080
{/if}
81-
{#if $todos.isSuccess}
81+
{#if todos.isSuccess}
8282
<ul>
83-
{#each $todos.data.items as item}
83+
{#each todos.data.items as item}
8484
<li>{item}</li>
8585
{/each}
8686
</ul>
8787
<div>
88-
<button on:click={() => $clearMutation.mutate(undefined)}>
89-
Clear All
90-
</button>
91-
</div>
92-
{/if}
93-
{#if $todos.isFetching}
94-
<div style="color:darkgreen; font-weight:700">
95-
'Background Updating...' : ' '
88+
<button onclick={() => clearMutation.mutate(undefined)}> Clear All </button>
9689
</div>
9790
{/if}
9891

92+
<pre
93+
class={['updating-text', todos.isFetching && 'on']}
94+
style="font-weight:700">Background Updating...</pre>
95+
9996
<style>
10097
li {
10198
text-align: left;
10299
}
100+
101+
.updating-text {
102+
color: transparent;
103+
transition: all 0.3s ease;
104+
}
105+
.updating-text.on {
106+
color: green;
107+
transition: none;
108+
}
103109
</style>

0 commit comments

Comments
 (0)