diff --git a/docs/svelte/reactivity.md b/docs/svelte/reactivity.md index c4c8a105f9..675e063b42 100644 --- a/docs/svelte/reactivity.md +++ b/docs/svelte/reactivity.md @@ -3,18 +3,18 @@ id: reactivity title: Reactivity --- -Svelte uses a compiler to build your code which optimises rendering. By default, variables will run once, unless they are referenced in your markup. To be able to react to changes in options you need to use [stores](https://svelte.dev/docs/svelte-store). +Svelte uses a compiler to build your code which optimises rendering. By default, components run once, unless they are referenced in your markup. To be able to react to changes in options you need to use [stores](https://svelte.dev/docs/svelte-store). -In the below example, the `refetchInterval` option is set from the variable `intervalMs`, which is edited by the input field. However, as the query is not told it should react to changes in `intervalMs`, `refetchInterval` will not change when the input value changes. +In the below example, the `refetchInterval` option is set from the variable `intervalMs`, which is bound to the input field. However, as the query is not able to react to changes in `intervalMs`, `refetchInterval` will not change when the input value changes. ```markdown - - + ``` -To solve this, create a store for the options and use it as input for the query. Update the options store when the value changes and the query will react to the change. +To solve this, we can convert `intervalMs` into a writable store. The query options can then be turned into a derived store, which will be passed into the function with true reactivity. ```markdown - - + ``` diff --git a/packages/svelte-query/src/__tests__/CreateQuery.svelte b/packages/svelte-query/src/__tests__/CreateQuery.svelte index c702c66814..05029b0b81 100644 --- a/packages/svelte-query/src/__tests__/CreateQuery.svelte +++ b/packages/svelte-query/src/__tests__/CreateQuery.svelte @@ -1,15 +1,12 @@ {#if $query.isPending} @@ -17,11 +14,11 @@ {:else if $query.isError}

Error

{:else if $query.isSuccess} -

Success

+ {#if Array.isArray($query.data)} + {#each $query.data as item} +

{item}

+ {/each} + {:else} +

{$query.data}

+ {/if} {/if} - - diff --git a/packages/svelte-query/src/__tests__/createQuery.test.ts b/packages/svelte-query/src/__tests__/createQuery.test.ts index 47be18395d..373494e179 100644 --- a/packages/svelte-query/src/__tests__/createQuery.test.ts +++ b/packages/svelte-query/src/__tests__/createQuery.test.ts @@ -1,9 +1,9 @@ import { describe, expect, test } from 'vitest' import { render, waitFor } from '@testing-library/svelte' import { derived, writable } from 'svelte/store' +import { QueryClient } from '@tanstack/query-core' import CreateQuery from './CreateQuery.svelte' import { sleep } from './utils' -import type { CreateQueryOptions } from '../types' describe('createQuery', () => { test('Render and wait for success', async () => { @@ -16,94 +16,142 @@ describe('createQuery', () => { return 'Success' }, }, + queryClient: new QueryClient(), }, }) await waitFor(() => { - expect(rendered.getByText('Loading')).toBeInTheDocument() + expect(rendered.queryByText('Loading')).toBeInTheDocument() }) await waitFor(() => { - expect(rendered.getByText('Success')).toBeInTheDocument() + expect(rendered.queryByText('Success')).toBeInTheDocument() }) }) - test('Keep previous data when returned as placeholder data', async () => { - const options = writable({ - queryKey: ['test', [1]], - queryFn: async ({ queryKey }) => { + test('Accept a writable store for options', async () => { + const optionsStore = writable({ + queryKey: ['test'], + queryFn: async () => { await sleep(10) - const ids = queryKey[1] - if (!ids || !Array.isArray(ids)) return [] - return ids.map((id) => ({ id })) + return 'Success' }, - placeholderData: (previousData: { id: number }[]) => previousData, - }) satisfies CreateQueryOptions - - const rendered = render(CreateQuery, { props: { options } }) + }) - await waitFor(() => { - expect(rendered.queryByText('id: 1')).not.toBeInTheDocument() - expect(rendered.queryByText('id: 2')).not.toBeInTheDocument() + const rendered = render(CreateQuery, { + props: { + options: optionsStore, + queryClient: new QueryClient(), + }, }) await waitFor(() => { - expect(rendered.queryByText('id: 1')).toBeInTheDocument() - expect(rendered.queryByText('id: 2')).not.toBeInTheDocument() + expect(rendered.queryByText('Success')).toBeInTheDocument() }) + }) + + test('Accept a derived store for options', async () => { + const writableStore = writable('test') - options.update((o) => ({ ...o, queryKey: ['test', [1, 2]] })) + const derivedStore = derived(writableStore, ($store) => ({ + queryKey: [$store], + queryFn: async () => { + await sleep(10) + return 'Success' + }, + })) - await waitFor(() => { - expect(rendered.queryByText('id: 1')).toBeInTheDocument() - expect(rendered.queryByText('id: 2')).not.toBeInTheDocument() + const rendered = render(CreateQuery, { + props: { + options: derivedStore, + queryClient: new QueryClient(), + }, }) await waitFor(() => { - expect(rendered.queryByText('id: 1')).toBeInTheDocument() - expect(rendered.queryByText('id: 2')).toBeInTheDocument() + expect(rendered.queryByText('Success')).toBeInTheDocument() }) }) - test('Accept a writable store for options', async () => { - const optionsStore = writable({ - queryKey: ['test'], + test('Ensure reactivity when queryClient defaults are set', async () => { + const writableStore = writable(1) + + const derivedStore = derived(writableStore, ($store) => ({ + queryKey: [$store], queryFn: async () => { await sleep(10) - return 'Success' + return `Success ${$store}` }, - }) satisfies CreateQueryOptions + })) const rendered = render(CreateQuery, { props: { - options: optionsStore, + options: derivedStore, + queryClient: new QueryClient({ + defaultOptions: { queries: { staleTime: 60 * 1000 } }, + }), }, }) await waitFor(() => { - expect(rendered.getByText('Success')).toBeInTheDocument() + expect(rendered.queryByText('Success 1')).toBeInTheDocument() + expect(rendered.queryByText('Success 2')).not.toBeInTheDocument() + }) + + writableStore.set(2) + + await waitFor(() => { + expect(rendered.queryByText('Success 1')).not.toBeInTheDocument() + expect(rendered.queryByText('Success 2')).toBeInTheDocument() + }) + + writableStore.set(1) + + await waitFor(() => { + expect(rendered.queryByText('Success 1')).toBeInTheDocument() + expect(rendered.queryByText('Success 2')).not.toBeInTheDocument() }) }) - test('Accept a derived store for options', async () => { - const writableStore = writable('test') + test('Keep previous data when returned as placeholder data', async () => { + const writableStore = writable([1]) const derivedStore = derived(writableStore, ($store) => ({ - queryKey: [$store], + queryKey: ['test', $store], queryFn: async () => { await sleep(10) - return 'Success' + return $store.map((id) => `Success ${id}`) }, - })) satisfies CreateQueryOptions + placeholderData: (previousData: string) => previousData, + })) const rendered = render(CreateQuery, { props: { options: derivedStore, + queryClient: new QueryClient(), }, }) await waitFor(() => { - expect(rendered.getByText('Success')).toBeInTheDocument() + expect(rendered.queryByText('Success 1')).not.toBeInTheDocument() + expect(rendered.queryByText('Success 2')).not.toBeInTheDocument() + }) + + await waitFor(() => { + expect(rendered.queryByText('Success 1')).toBeInTheDocument() + expect(rendered.queryByText('Success 2')).not.toBeInTheDocument() + }) + + writableStore.set([1, 2]) + + await waitFor(() => { + expect(rendered.queryByText('Success 1')).toBeInTheDocument() + expect(rendered.queryByText('Success 2')).not.toBeInTheDocument() + }) + + await waitFor(() => { + expect(rendered.queryByText('Success 1')).toBeInTheDocument() + expect(rendered.queryByText('Success 2')).toBeInTheDocument() }) }) })