diff --git a/docs/config.json b/docs/config.json index 556ed2b67d..aa16e1f47f 100644 --- a/docs/config.json +++ b/docs/config.json @@ -526,6 +526,35 @@ } ] }, + { + "label": "svelte", + "children": [ + { + "label": "Queries", + "to": "framework/svelte/guides/queries" + }, + { + "label": "Network Mode", + "to": "framework/svelte/guides/network-mode" + }, + { + "label": "Mutations", + "to": "framework/svelte/guides/mutations" + }, + { + "label": "Invalidations from Mutations", + "to": "framework/svelte/guides/invalidations-from-mutations" + }, + { + "label": "Optimistic Updates", + "to": "framework/svelte/guides/optimistic-updates" + }, + { + "label": "Query Cancellation", + "to": "framework/svelte/guides/query-cancellation" + } + ] + }, { "label": "vue", "children": [ diff --git a/docs/framework/svelte/guides/invalidations-from-mutations.md b/docs/framework/svelte/guides/invalidations-from-mutations.md new file mode 100644 index 0000000000..3e3c4bb4ee --- /dev/null +++ b/docs/framework/svelte/guides/invalidations-from-mutations.md @@ -0,0 +1,14 @@ +--- +id: invalidations-from-mutations +title: Invalidations from Mutations +ref: docs/framework/react/guides/invalidations-from-mutations.md +replace: + { + 'React': 'Svelte', + '@tanstack/react-query': '@tanstack/svelte-query', + 'useMutation[(]': 'createMutation(() => ', + 'useMutation': 'createMutation', + 'useQuery[(]': 'createQuery(() => ', + 'hook': 'function', + } +--- diff --git a/docs/framework/svelte/guides/mutations.md b/docs/framework/svelte/guides/mutations.md new file mode 100644 index 0000000000..26d053cdf6 --- /dev/null +++ b/docs/framework/svelte/guides/mutations.md @@ -0,0 +1,263 @@ +--- +id: mutations +title: Mutations +ref: docs/framework/react/guides/mutations.md +replace: { 'useMutation': 'createMutation', 'hook': 'function' } +--- + +[//]: # 'Example' + +```svelte + + +
+ {#if mutation.isPending} + Adding todo... + {:else if mutation.isError} +
An error occurred: {mutation.error.message}
+ {:else if mutation.isSuccess} +
Todo added!
+ {/if} + +
+``` + +[//]: # 'Example' +[//]: # 'Info1' +[//]: # 'Info1' +[//]: # 'Example2' +[//]: # 'Example2' +[//]: # 'Example3' + +```svelte + + +
{ + e.preventDefault() + mutation.mutate({ title: title }) + }} +> + {#if mutation.error} +
mutation.reset()}>{mutation.error}
+ {/if} + (title = e.currentTarget.value)} + /> +
+ +
+``` + +[//]: # 'Example3' +[//]: # 'Example4' + +```ts +createMutation(() => ({ + mutationFn: addTodo, + onMutate: (variables, context) => { + // A mutation is about to happen! + + // Optionally return a result containing data to use when for example rolling back + return { id: 1 } + }, + onError: (error, variables, onMutateResult, context) => { + // An error happened! + console.log(`rolling back optimistic update with id ${onMutateResult.id}`) + }, + onSuccess: (data, variables, onMutateResult, context) => { + // Boom baby! + }, + onSettled: (data, error, variables, onMutateResult, context) => { + // Error or success... doesn't matter! + }, +})) +``` + +[//]: # 'Example4' +[//]: # 'Example5' + +```ts +createMutation(() => ({ + mutationFn: addTodo, + onSuccess: async () => { + console.log("I'm first!") + }, + onSettled: async () => { + console.log("I'm second!") + }, +})) +``` + +[//]: # 'Example5' +[//]: # 'Example6' + +```ts +const mutation = createMutation(() => ({ + mutationFn: addTodo, + onSuccess: (data, variables, onMutateResult, context) => { + // I will fire first + }, + onError: (error, variables, onMutateResult, context) => { + // I will fire first + }, + onSettled: (data, error, variables, onMutateResult, context) => { + // I will fire first + }, +})) + +mutation.mutate(todo, { + onSuccess: (data, variables, onMutateResult, context) => { + // I will fire second! + }, + onError: (error, variables, onMutateResult, context) => { + // I will fire second! + }, + onSettled: (data, error, variables, onMutateResult, context) => { + // I will fire second! + }, +}) +``` + +[//]: # 'Example6' +[//]: # 'Example7' + +```ts +const mutation = createMutation(() => ({ + mutationFn: addTodo, + onSuccess: (data, variables, onMutateResult, context) => { + // Will be called 3 times + }, +})) + +const todos = ['Todo 1', 'Todo 2', 'Todo 3'] +todos.forEach((todo) => { + mutation.mutate(todo, { + onSuccess: (data, variables, onMutateResult, context) => { + // Will execute only once, for the last mutation (Todo 3), + // regardless which mutation resolves first + }, + }) +}) +``` + +[//]: # 'Example7' +[//]: # 'Example8' + +```ts +const mutation = createMutation(() => ({ mutationFn: addTodo })) + +try { + const todo = await mutation.mutateAsync(todo) + console.log(todo) +} catch (error) { + console.error(error) +} finally { + console.log('done') +} +``` + +[//]: # 'Example8' +[//]: # 'Example9' + +```ts +const mutation = createMutation(() => ({ + mutationFn: addTodo, + retry: 3, +})) +``` + +[//]: # 'Example9' +[//]: # 'Example10' + +```ts +const queryClient = new QueryClient() + +// Define the "addTodo" mutation +queryClient.setMutationDefaults(['addTodo'], { + mutationFn: addTodo, + onMutate: async (variables, context) => { + // Cancel current queries for the todos list + await context.client.cancelQueries({ queryKey: ['todos'] }) + + // Create optimistic todo + const optimisticTodo = { id: uuid(), title: variables.title } + + // Add optimistic todo to todos list + context.client.setQueryData(['todos'], (old) => [...old, optimisticTodo]) + + // Return a result with the optimistic todo + return { optimisticTodo } + }, + onSuccess: (result, variables, onMutateResult, context) => { + // Replace optimistic todo in the todos list with the result + context.client.setQueryData(['todos'], (old) => + old.map((todo) => + todo.id === onMutateResult.optimisticTodo.id ? result : todo, + ), + ) + }, + onError: (error, variables, onMutateResult, context) => { + // Remove optimistic todo from the todos list + context.client.setQueryData(['todos'], (old) => + old.filter((todo) => todo.id !== onMutateResult.optimisticTodo.id), + ) + }, + retry: 3, +}) + +// Start mutation in some component: +const mutation = createMutation(() => ({ mutationKey: ['addTodo'] })) +mutation.mutate({ title: 'title' }) + +// If the mutation has been paused because the device is for example offline, +// Then the paused mutation can be dehydrated when the application quits: +const state = dehydrate(queryClient) + +// The mutation can then be hydrated again when the application is started: +hydrate(queryClient, state) + +// Resume the paused mutations: +queryClient.resumePausedMutations() +``` + +[//]: # 'Example10' +[//]: # 'PersistOfflineIntro' +[//]: # 'PersistOfflineIntro' +[//]: # 'Example11' +[//]: # 'Example11' +[//]: # 'OfflineExampleLink' +[//]: # 'OfflineExampleLink' +[//]: # 'ExampleScopes' + +```ts +const mutation = createMutation(() => ({ + mutationFn: addTodo, + scope: { + id: 'todo', + }, +})) +``` + +[//]: # 'ExampleScopes' +[//]: # 'Materials' +[//]: # 'Materials' diff --git a/docs/framework/svelte/guides/network-mode.md b/docs/framework/svelte/guides/network-mode.md new file mode 100644 index 0000000000..e773e73ff0 --- /dev/null +++ b/docs/framework/svelte/guides/network-mode.md @@ -0,0 +1,5 @@ +--- +id: network-mode +title: Network Mode +ref: docs/framework/react/guides/network-mode.md +--- diff --git a/docs/framework/svelte/guides/optimistic-updates.md b/docs/framework/svelte/guides/optimistic-updates.md new file mode 100644 index 0000000000..b63ca89f5d --- /dev/null +++ b/docs/framework/svelte/guides/optimistic-updates.md @@ -0,0 +1,149 @@ +--- +id: optimistic-updates +title: Optimistic Updates +ref: docs/framework/react/guides/optimistic-updates.md +replace: + { 'React': 'Svelte', 'useMutation': 'createMutation', 'hook': 'function' } +--- + +[//]: # 'ExampleUI1' + +```ts +const addTodoMutation = createMutation(() => ({ + mutationFn: (newTodo: string) => axios.post('/api/data', { text: newTodo }), + // make sure to _return_ the Promise from the query invalidation + // so that the mutation stays in `pending` state until the refetch is finished + onSettled: () => queryClient.invalidateQueries({ queryKey: ['todos'] }), +})) +``` + +[//]: # 'ExampleUI1' +[//]: # 'ExampleUI2' + +```svelte + +``` + +[//]: # 'ExampleUI2' +[//]: # 'ExampleUI3' + +```svelte +{#if addTodoMutation.isError} +
  • + {addTodoMutation.variables} + +
  • +{/if} +``` + +[//]: # 'ExampleUI3' +[//]: # 'ExampleUI4' + +```ts +// somewhere in your app +const mutation = createMutation(() => ({ + mutationFn: (newTodo: string) => axios.post('/api/data', { text: newTodo }), + onSettled: () => queryClient.invalidateQueries({ queryKey: ['todos'] }), + mutationKey: ['addTodo'], +})) + +// access variables somewhere else +const variables = useMutationState({ + filters: { mutationKey: ['addTodo'], status: 'pending' }, + select: (mutation) => mutation.state.variables, +}) +``` + +[//]: # 'ExampleUI4' +[//]: # 'Example' + +```ts +const queryClient = useQueryClient() + +createMutation(() => ({ + mutationFn: updateTodo, + // When mutate is called: + onMutate: async (newTodo, context) => { + // Cancel any outgoing refetches + // (so they don't overwrite our optimistic update) + await context.client.cancelQueries({ queryKey: ['todos'] }) + + // Snapshot the previous value + const previousTodos = context.client.getQueryData(['todos']) + + // Optimistically update to the new value + context.client.setQueryData(['todos'], (old) => [...(old ?? []), newTodo]) + + // Return a result with the snapshotted value + return { previousTodos } + }, + // If the mutation fails, + // use the result returned from onMutate to roll back + onError: (err, newTodo, onMutateResult, context) => { + context.client.setQueryData(['todos'], onMutateResult.previousTodos) + }, + // Always refetch after error or success: + onSettled: (data, error, variables, onMutateResult, context) => + context.client.invalidateQueries({ queryKey: ['todos'] }), +})) +``` + +[//]: # 'Example' +[//]: # 'Example2' + +```ts +createMutation(() => ({ + mutationFn: updateTodo, + // When mutate is called: + onMutate: async (newTodo, context) => { + // Cancel any outgoing refetches + // (so they don't overwrite our optimistic update) + await context.client.cancelQueries({ queryKey: ['todos', newTodo.id] }) + + // Snapshot the previous value + const previousTodo = context.client.getQueryData(['todos', newTodo.id]) + + // Optimistically update to the new value + context.client.setQueryData(['todos', newTodo.id], newTodo) + + // Return a result with the previous and new todo + return { previousTodo, newTodo } + }, + // If the mutation fails, use the result we returned above + onError: (err, newTodo, onMutateResult, context) => { + context.client.setQueryData( + ['todos', onMutateResult?.newTodo.id], + onMutateResult?.previousTodo, + ) + }, + // Always refetch after error or success: + onSettled: (newTodo, error, variables, onMutateResult, context) => + context.client.invalidateQueries({ queryKey: ['todos', variables.id] }), +})) +``` + +[//]: # 'Example2' +[//]: # 'Example3' + +```ts +createMutation(() => ({ + mutationFn: updateTodo, + // ... + onSettled: async (newTodo, error, variables, onMutateResult, context) => { + if (error) { + // do something + } + }, +})) +``` + +[//]: # 'Example3' diff --git a/docs/framework/svelte/guides/queries.md b/docs/framework/svelte/guides/queries.md new file mode 100644 index 0000000000..52f4b865ff --- /dev/null +++ b/docs/framework/svelte/guides/queries.md @@ -0,0 +1,90 @@ +--- +id: queries +title: Queries +ref: docs/framework/react/guides/queries.md +replace: + { + 'React': 'Svelte', + 'react-query': 'svelte-query', + 'or custom hooks': '', + 'the `useQuery` hook': '`createQuery`', + 'useQuery': 'createQuery', + } +--- + +[//]: # 'Example' + +```svelte + +``` + +[//]: # 'Example' +[//]: # 'Example2' + +```ts +const todosQuery = createQuery(() => ({ + queryKey: ['todos'], + queryFn: fetchTodoList, +})) +``` + +[//]: # 'Example2' +[//]: # 'Example3' + +```svelte + + +
    + {#if todosQuery.isPending} +

    Loading...

    + {:else if todosQuery.isError} +

    Error: {todosQuery.error.message}

    + {:else if todosQuery.isSuccess} + {#each todosQuery.data as todo} +

    {todo.title}

    + {/each} + {/if} +
    +``` + +[//]: # 'Example3' + +If booleans aren't your thing, you can always use the `status` state as well: + +[//]: # 'Example4' + +```svelte + + +
    + {#if todosQuery.status === 'pending'} +

    Loading...

    + {:else if todosQuery.status === 'error'} +

    Error: {todosQuery.error.message}

    + {:else if todosQuery.status === 'success'} + {#each todosQuery.data as todo} +

    {todo.title}

    + {/each} + {/if} +
    +``` + +[//]: # 'Example4' +[//]: # 'Materials' +[//]: # 'Materials' diff --git a/docs/framework/svelte/guides/query-cancellation.md b/docs/framework/svelte/guides/query-cancellation.md new file mode 100644 index 0000000000..b38323ca3d --- /dev/null +++ b/docs/framework/svelte/guides/query-cancellation.md @@ -0,0 +1,160 @@ +--- +id: query-cancellation +title: Query Cancellation +ref: docs/framework/react/guides/query-cancellation.md +replace: + { + '@tanstack/react-query': '@tanstack/svelte-query', + 'useQuery[(]': 'createQuery(() => ', + } +--- + +[//]: # 'Example' + +```ts +const todosQuery = createQuery(() => ({ + queryKey: ['todos'], + queryFn: async ({ signal }) => { + const todosResponse = await fetch('/todos', { + // Pass the signal to one fetch + signal, + }) + const todos = await todosResponse.json() + + const todoDetails = todos.map(async ({ details }) => { + const response = await fetch(details, { + // Or pass it to several + signal, + }) + return response.json() + }) + + return Promise.all(todoDetails) + }, +})) +``` + +[//]: # 'Example' +[//]: # 'Example2' + +```ts +import axios from 'axios' + +const todosQuery = createQuery(() => ({ + queryKey: ['todos'], + queryFn: ({ signal }) => + axios.get('/todos', { + // Pass the signal to `axios` + signal, + }), +})) +``` + +[//]: # 'Example2' +[//]: # 'Example3' + +```ts +import axios from 'axios' + +const todosQuery = createQuery(() => ({ + queryKey: ['todos'], + queryFn: ({ signal }) => { + // Create a new CancelToken source for this request + const CancelToken = axios.CancelToken + const source = CancelToken.source() + + const promise = axios.get('/todos', { + // Pass the source token to your request + cancelToken: source.token, + }) + + // Cancel the request if TanStack Query signals to abort + signal?.addEventListener('abort', () => { + source.cancel('Query was cancelled by TanStack Query') + }) + + return promise + }, +})) +``` + +[//]: # 'Example3' +[//]: # 'Example4' + +```ts +const todosQuery = createQuery(() => ({ + queryKey: ['todos'], + queryFn: ({ signal }) => { + return new Promise((resolve, reject) => { + var oReq = new XMLHttpRequest() + oReq.addEventListener('load', () => { + resolve(JSON.parse(oReq.responseText)) + }) + signal?.addEventListener('abort', () => { + oReq.abort() + reject() + }) + oReq.open('GET', '/todos') + oReq.send() + }) + }, +})) +``` + +[//]: # 'Example4' +[//]: # 'Example5' + +```ts +const client = new GraphQLClient(endpoint) + +const todosQuery = createQuery(() => ({ + queryKey: ['todos'], + queryFn: ({ signal }) => { + client.request({ document: query, signal }) + }, +})) +``` + +[//]: # 'Example5' +[//]: # 'Example6' + +```ts +const todosQuery = createQuery(() => ({ + queryKey: ['todos'], + queryFn: ({ signal }) => { + const client = new GraphQLClient(endpoint, { + signal, + }) + return client.request(query, variables) + }, +})) +``` + +[//]: # 'Example6' +[//]: # 'Example7' + +```svelte + + + +``` + +[//]: # 'Example7' +[//]: # 'Limitations' +[//]: # 'Limitations'