Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 5 additions & 7 deletions docs/svelte/reactivity.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ 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/tutorial/writable-stores).
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).

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.

Expand All @@ -30,20 +30,18 @@ To solve this, create a store for the options and use it as input for the query.
```markdown
<script>
import { createQuery } from '@tanstack/svelte-query'
import type { CreateQueryOptions } from '@tanstack/svelte-query'

const endpoint = 'http://localhost:5173/api/data'

const queryOptions = writable({
queryKey: ['refetch'],
queryFn: async () => await fetch(endpoint).then((r) => r.json()),
refetchInterval: 1000,
})
const query = createQuery(queryOptions)
}) satisfies CreateQueryOptions

function updateRefetchInterval(event) {
$queryOptions.refetchInterval = event.target.valueAsNumber
}
const query = createQuery(queryOptions)
</script>

<input type="number" on:input={updateRefetchInterval} />
<input type="number" bind:value={$queryOptions.refetchInterval} />
```
55 changes: 49 additions & 6 deletions packages/svelte-query/src/__tests__/createQuery.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { describe, it, expect } from 'vitest'
import { describe, expect, test } from 'vitest'
import { render, waitFor } from '@testing-library/svelte'
import { writable } from 'svelte/store'
import { derived, writable } from 'svelte/store'
import CreateQuery from './CreateQuery.svelte'
import { sleep } from './utils'
import type { CreateQueryOptions } from '../types'

describe('createQuery', () => {
it('Render and wait for success', async () => {
test('Render and wait for success', async () => {
const rendered = render(CreateQuery, {
props: {
options: {
Expand All @@ -28,8 +28,8 @@ describe('createQuery', () => {
})
})

it('should keep previous data when returned as placeholder data', async () => {
const options: CreateQueryOptions = writable({
test('Keep previous data when returned as placeholder data', async () => {
const options = writable({
queryKey: ['test', [1]],
queryFn: async ({ queryKey }) => {
await sleep(10)
Expand All @@ -38,7 +38,8 @@ describe('createQuery', () => {
return ids.map((id) => ({ id }))
},
placeholderData: (previousData: { id: number }[]) => previousData,
})
}) satisfies CreateQueryOptions

const rendered = render(CreateQuery, { props: { options } })

await waitFor(() => {
Expand All @@ -63,4 +64,46 @@ describe('createQuery', () => {
expect(rendered.queryByText('id: 2')).toBeInTheDocument()
})
})

test('Accept a writable store for options', async () => {
const optionsStore = writable({
queryKey: ['test'],
queryFn: async () => {
await sleep(10)
return 'Success'
},
}) satisfies CreateQueryOptions

const rendered = render(CreateQuery, {
props: {
options: optionsStore,
},
})

await waitFor(() => {
expect(rendered.getByText('Success')).toBeInTheDocument()
})
})

test('Accept a derived store for options', async () => {
const writableStore = writable('test')

const derivedStore = derived(writableStore, ($store) => ({
queryKey: [$store],
queryFn: async () => {
await sleep(10)
return 'Success'
},
})) satisfies CreateQueryOptions

const rendered = render(CreateQuery, {
props: {
options: derivedStore,
},
})

await waitFor(() => {
expect(rendered.getByText('Success')).toBeInTheDocument()
})
})
})
6 changes: 3 additions & 3 deletions packages/svelte-query/src/createBaseQuery.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { derived, get, readable, writable } from 'svelte/store'
import { derived, get, readable } from 'svelte/store'
import { notifyManager } from '@tanstack/query-core'
import type { QueryClient, QueryKey, QueryObserver } from '@tanstack/query-core'
import type { CreateBaseQueryOptions, CreateBaseQueryResult } from './types'
import { useQueryClient } from './useQueryClient'
import { isWritable } from './utils'
import { isSvelteStore } from './utils'

export function createBaseQuery<
TQueryFnData,
Expand All @@ -24,7 +24,7 @@ export function createBaseQuery<
): CreateBaseQueryResult<TData, TError> {
const client = useQueryClient(queryClient)

const optionsStore = isWritable(options) ? options : writable(options)
const optionsStore = isSvelteStore(options) ? options : readable(options)

const defaultedOptionsStore = derived(optionsStore, ($options) => {
const defaultedOptions = client.defaultQueryOptions($options)
Expand Down
6 changes: 3 additions & 3 deletions packages/svelte-query/src/createMutation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { readable, derived, writable, get } from 'svelte/store'
import { readable, derived, get } from 'svelte/store'
import type { QueryClient, DefaultError } from '@tanstack/query-core'
import { MutationObserver, notifyManager } from '@tanstack/query-core'
import type {
Expand All @@ -7,7 +7,7 @@ import type {
CreateMutationResult,
} from './types'
import { useQueryClient } from './useQueryClient'
import { isWritable } from './utils'
import { isSvelteStore } from './utils'

export function createMutation<
TData = unknown,
Expand All @@ -20,7 +20,7 @@ export function createMutation<
): CreateMutationResult<TData, TError, TVariables, TContext> {
const client = useQueryClient(queryClient)

const optionsStore = isWritable(options) ? options : writable(options)
const optionsStore = isSvelteStore(options) ? options : readable(options)

const observer = new MutationObserver<TData, TError, TVariables, TContext>(
client,
Expand Down
11 changes: 6 additions & 5 deletions packages/svelte-query/src/createQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import type {
QueryObserverOptions,
} from '@tanstack/query-core'
import { notifyManager, QueriesObserver } from '@tanstack/query-core'
import { derived, get, readable, writable, type Readable } from 'svelte/store'
import type { WritableOrVal } from './types'
import { derived, get, readable } from 'svelte/store'
import type { Readable } from 'svelte/store'
import type { StoreOrVal } from './types'
import { useQueryClient } from './useQueryClient'
import { isWritable } from './utils'
import { isSvelteStore } from './utils'

// This defines the `CreateQueryOptions` that are accepted in `QueriesOptions` & `GetOptions`.
// `placeholderData` function does not have a parameter
Expand Down Expand Up @@ -168,15 +169,15 @@ export function createQueries<
queries,
...options
}: {
queries: WritableOrVal<[...QueriesOptions<T>]>
queries: StoreOrVal<[...QueriesOptions<T>]>
combine?: (result: QueriesResults<T>) => TCombinedResult
},
queryClient?: QueryClient,
): Readable<TCombinedResult> {
const client = useQueryClient(queryClient)
// const isRestoring = useIsRestoring()

const queriesStore = isWritable(queries) ? queries : writable(queries)
const queriesStore = isSvelteStore(queries) ? queries : readable(queries)

const defaultedQueriesStore = derived(queriesStore, ($queries) => {
return $queries.map((opts) => {
Expand Down
8 changes: 4 additions & 4 deletions packages/svelte-query/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type {
import type { Readable, Writable } from 'svelte/store'

/** Allows a type to be either the base object or a store of that object */
export type WritableOrVal<T> = T | Writable<T>
export type StoreOrVal<T> = T | Readable<T> | Writable<T>

/** Options for createBaseQuery */
export type CreateBaseQueryOptions<
Expand All @@ -22,7 +22,7 @@ export type CreateBaseQueryOptions<
TData = TQueryFnData,
TQueryData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
> = WritableOrVal<
> = StoreOrVal<
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>
>

Expand Down Expand Up @@ -54,7 +54,7 @@ export type CreateInfiniteQueryOptions<
TQueryData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
TPageParam = unknown,
> = WritableOrVal<
> = StoreOrVal<
InfiniteQueryObserverOptions<
TQueryFnData,
TError,
Expand Down Expand Up @@ -89,7 +89,7 @@ export type CreateMutationOptions<
TError = DefaultError,
TVariables = void,
TContext = unknown,
> = WritableOrVal<
> = StoreOrVal<
Omit<
MutationObserverOptions<TData, TError, TVariables, TContext>,
'_defaulted' | 'variables'
Expand Down
3 changes: 2 additions & 1 deletion packages/svelte-query/src/useIsFetching.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import {
type QueryClient,
notifyManager,
} from '@tanstack/query-core'
import { type Readable, readable } from 'svelte/store'
import { readable } from 'svelte/store'
import type { Readable } from 'svelte/store'
import { useQueryClient } from './useQueryClient'

export function useIsFetching(
Expand Down
3 changes: 2 additions & 1 deletion packages/svelte-query/src/useIsMutating.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import {
type QueryClient,
notifyManager,
} from '@tanstack/query-core'
import { type Readable, readable } from 'svelte/store'
import { readable } from 'svelte/store'
import type { Readable } from 'svelte/store'
import { useQueryClient } from './useQueryClient'

export function useIsMutating(
Expand Down
12 changes: 6 additions & 6 deletions packages/svelte-query/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { Writable } from 'svelte/store'
import type { WritableOrVal } from './types'
import type { Readable } from 'svelte/store'
import type { StoreOrVal } from './types'

export function isWritable<T extends object>(
obj: WritableOrVal<T>,
): obj is Writable<T> {
return 'subscribe' in obj && 'set' in obj && 'update' in obj
export function isSvelteStore<T extends object>(
obj: StoreOrVal<T>,
): obj is Readable<T> {
return 'subscribe' in obj && typeof obj.subscribe === 'function'
}
5 changes: 4 additions & 1 deletion packages/svelte-query/vitest.setup.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import matchers from '@testing-library/jest-dom/matchers'
import { expect } from 'vitest'
import { cleanup } from '@testing-library/svelte'
import { afterEach, expect } from 'vitest'

expect.extend(matchers)

afterEach(() => cleanup())