snapshot of https://github.com/TanStack/query.git (main, 2026-09-16; blobs > 131072B omitted)
Some checks failed
autofix.ci / autofix (push) Failing after 4s
Release / Release (push) Has been skipped
GitHub Actions Security Analysis / Run zizmor (push) Failing after 2s

This commit is contained in:
upstream-mirror
2026-09-16 01:13:08 +05:00
commit 3a013ccdfe
3570 changed files with 490194 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
---
id: advanced-ssr
title: Advanced Server Rendering
---
Will come soon

View File

@@ -0,0 +1,57 @@
---
id: background-fetching-indicators
title: Background Fetching Indicators
ref: docs/framework/react/guides/background-fetching-indicators.md
replace: { 'hook': 'function' }
---
[//]: # 'Example'
```tsx
import { Switch, Match, Show, For } from 'solid-js'
function Todos() {
const todosQuery = useQuery(() => ({
queryKey: ['todos'],
queryFn: fetchTodos,
}))
return (
<Switch>
<Match when={todosQuery.isPending}>
<span>Loading...</span>
</Match>
<Match when={todosQuery.isError}>
<span>Error: {todosQuery.error.message}</span>
</Match>
<Match when={todosQuery.isSuccess}>
<Show when={todosQuery.isFetching}>
<div>Refreshing...</div>
</Show>
<div>
<For each={todosQuery.data}>{(todo) => <Todo todo={todo} />}</For>
</div>
</Match>
</Switch>
)
}
```
[//]: # 'Example'
[//]: # 'Example2'
```tsx
import { useIsFetching } from '@tanstack/solid-query'
function GlobalLoadingIndicator() {
const isFetching = useIsFetching()
return (
<Show when={isFetching()}>
<div>Queries are fetching in the background...</div>
</Show>
)
}
```
[//]: # 'Example2'

View File

@@ -0,0 +1,10 @@
---
id: caching
title: Caching Examples
ref: docs/framework/react/guides/caching.md
replace:
{
'useQuery[(][{] queryKey': 'useQuery(() => ({ queryKey',
'fetchTodos [}][)]': 'fetchTodos }))',
}
---

View File

@@ -0,0 +1,53 @@
---
id: default-query-function
title: Default Query Function
ref: docs/framework/react/guides/default-query-function.md
---
[//]: # 'Example'
```tsx
// Define a default query function that will receive the query key
const defaultQueryFn = async ({ queryKey }) => {
const { data } = await axios.get(
`https://jsonplaceholder.typicode.com${queryKey[0]}`,
)
return data
}
// provide the default query function to your app with defaultOptions
const queryClient = new QueryClient({
defaultOptions: {
queries: {
queryFn: defaultQueryFn,
},
},
})
function App() {
return (
<QueryClientProvider client={queryClient}>
<YourApp />
</QueryClientProvider>
)
}
// All you have to do now is pass a key!
function Posts() {
const postsQuery = useQuery(() => ({ queryKey: ['/posts'] }))
// ...
}
// You can even leave out the queryFn and just go straight into options
function Post(props) {
const postQuery = useQuery(() => ({
queryKey: [`/posts/${props.postId}`],
enabled: !!props.postId,
}))
// ...
}
```
[//]: # 'Example'

View File

@@ -0,0 +1,51 @@
---
id: dependent-queries
title: Dependent Queries
ref: docs/framework/react/guides/dependent-queries.md
---
[//]: # 'Example'
```tsx
// Get the user
const userQuery = useQuery(() => ({
queryKey: ['user', email],
queryFn: getUserByEmail,
}))
const userId = () => userQuery.data?.id
// Then get the user's projects
const projectsQuery = useQuery(() => ({
queryKey: ['projects', userId()],
queryFn: getProjectsByUser,
// The query will not execute until the userId exists
enabled: !!userId(),
}))
```
[//]: # 'Example'
[//]: # 'Example2'
```tsx
// Get the users ids
const usersQuery = useQuery(() => ({
queryKey: ['users'],
queryFn: getUsersData,
select: (users) => users.map((user) => user.id),
}))
// Then get the users messages
const usersMessages = useQueries(() => ({
queries: usersQuery.data
? usersQuery.data.map((id) => {
return {
queryKey: ['messages', id],
queryFn: () => getMessagesByUsers(id),
}
})
: [], // if usersQuery.data is undefined, an empty array will be returned
}))
```
[//]: # 'Example2'

View File

@@ -0,0 +1,102 @@
---
id: disabling-queries
title: Disabling/Pausing Queries
ref: docs/framework/react/guides/disabling-queries.md
---
[//]: # 'Example'
```tsx
import { Switch, Match, Show, For } from 'solid-js'
function Todos() {
const todosQuery = useQuery(() => ({
queryKey: ['todos'],
queryFn: fetchTodoList,
enabled: false,
}))
return (
<div>
<button onClick={() => todosQuery.refetch()}>Fetch Todos</button>
<Switch>
<Match when={todosQuery.data}>
<ul>
<For each={todosQuery.data}>{(todo) => <li>{todo.title}</li>}</For>
</ul>
</Match>
<Match when={todosQuery.isError}>
<span>Error: {todosQuery.error.message}</span>
</Match>
<Match when={todosQuery.isLoading}>
<span>Loading...</span>
</Match>
<Match when={true}>
<span>Not ready ...</span>
</Match>
</Switch>
<div>{todosQuery.isFetching ? 'Fetching...' : null}</div>
</div>
)
}
```
[//]: # 'Example'
[//]: # 'Example2'
```tsx
import { createSignal } from 'solid-js'
function Todos() {
const [filter, setFilter] = createSignal('')
const todosQuery = useQuery(() => ({
queryKey: ['todos', filter()],
queryFn: () => fetchTodos(filter()),
// ⬇️ disabled as long as the filter is empty
enabled: !!filter(),
}))
return (
<div>
{/* 🚀 applying the filter will enable and execute the query */}
<FiltersForm onApply={setFilter} />
<Show when={todosQuery.data}>
<TodosTable data={todosQuery.data} />
</Show>
</div>
)
}
```
[//]: # 'Example2'
[//]: # 'Example3'
```tsx
import { createSignal } from 'solid-js'
import { skipToken, useQuery } from '@tanstack/solid-query'
function Todos() {
const [filter, setFilter] = createSignal<string | undefined>()
const todosQuery = useQuery(() => ({
queryKey: ['todos', filter()],
// ⬇️ disabled as long as the filter is undefined or empty
queryFn: filter() ? () => fetchTodos(filter()!) : skipToken,
}))
return (
<div>
{/* 🚀 applying the filter will enable and execute the query */}
<FiltersForm onApply={setFilter} />
<Show when={todosQuery.data}>
<TodosTable data={todosQuery.data} />
</Show>
</div>
)
}
```
[//]: # 'Example3'

View File

@@ -0,0 +1,6 @@
---
id: does-this-replace-client-state
title: Does TanStack Query replace global state managers?
ref: docs/framework/react/guides/does-this-replace-client-state.md
replace: { 'hook': 'function' }
---

View File

@@ -0,0 +1,5 @@
---
id: filters
title: Filters
ref: docs/framework/react/guides/filters.md
---

View File

@@ -0,0 +1,13 @@
---
id: important-defaults
title: Important Defaults
ref: docs/framework/react/guides/important-defaults.md
---
[//]: # 'StructuralSharing'
- Query results by default are **structurally shared to detect if data has actually changed** and if not, **the data reference remains unchanged** to better help with value stabilization. If this concept sounds foreign, then don't worry about it! 99.9% of the time you will not need to disable this and it makes your app more performant at zero cost to you.
[//]: # 'StructuralSharing'
[//]: # 'Materials'
[//]: # 'Materials'

View File

@@ -0,0 +1,140 @@
---
id: infinite-queries
title: Infinite Queries
ref: docs/framework/react/guides/infinite-queries.md
---
[//]: # 'Example'
```tsx
import { Switch, Match, For, Show } from 'solid-js'
import { useInfiniteQuery } from '@tanstack/solid-query'
function Projects() {
const fetchProjects = async ({ pageParam }) => {
const res = await fetch('/api/projects?cursor=' + pageParam)
return res.json()
}
const projectsQuery = useInfiniteQuery(() => ({
queryKey: ['projects'],
queryFn: fetchProjects,
initialPageParam: 0,
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
}))
return (
<Switch>
<Match when={projectsQuery.isPending}>
<p>Loading...</p>
</Match>
<Match when={projectsQuery.isError}>
<p>Error: {projectsQuery.error.message}</p>
</Match>
<Match when={projectsQuery.isSuccess}>
<For each={projectsQuery.data.pages}>
{(group) => (
<For each={group.data}>{(project) => <p>{project.name}</p>}</For>
)}
</For>
<div>
<button
onClick={() => projectsQuery.fetchNextPage()}
disabled={!projectsQuery.hasNextPage || projectsQuery.isFetching}
>
{projectsQuery.isFetchingNextPage
? 'Loading more...'
: projectsQuery.hasNextPage
? 'Load More'
: 'Nothing more to load'}
</button>
</div>
<Show
when={projectsQuery.isFetching && !projectsQuery.isFetchingNextPage}
>
<div>Fetching...</div>
</Show>
</Match>
</Switch>
)
}
```
[//]: # 'Example'
[//]: # 'Example1'
```jsx
<List
onEndReached={() =>
projectsQuery.hasNextPage &&
!projectsQuery.isFetching &&
projectsQuery.fetchNextPage()
}
/>
```
[//]: # 'Example1'
[//]: # 'Example3'
```tsx
useInfiniteQuery(() => ({
queryKey: ['projects'],
queryFn: fetchProjects,
initialPageParam: 0,
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
getPreviousPageParam: (firstPage, pages) => firstPage.prevCursor,
}))
```
[//]: # 'Example3'
[//]: # 'Example4'
```tsx
useInfiniteQuery(() => ({
queryKey: ['projects'],
queryFn: fetchProjects,
select: (data) => ({
pages: [...data.pages].reverse(),
pageParams: [...data.pageParams].reverse(),
}),
}))
```
[//]: # 'Example4'
[//]: # 'Example8'
```tsx
useInfiniteQuery(() => ({
queryKey: ['projects'],
queryFn: fetchProjects,
initialPageParam: 0,
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
getPreviousPageParam: (firstPage, pages) => firstPage.prevCursor,
maxPages: 3,
}))
```
[//]: # 'Example8'
[//]: # 'Example9'
```tsx
return useInfiniteQuery(() => ({
queryKey: ['projects'],
queryFn: fetchProjects,
initialPageParam: 0,
getNextPageParam: (lastPage, allPages, lastPageParam) => {
if (lastPage.length === 0) {
return undefined
}
return lastPageParam + 1
},
getPreviousPageParam: (firstPage, allPages, firstPageParam) => {
if (firstPageParam <= 1) {
return undefined
}
return firstPageParam - 1
},
}))
```
[//]: # 'Example9'

View File

@@ -0,0 +1,127 @@
---
id: initial-query-data
title: Initial Query Data
ref: docs/framework/react/guides/initial-query-data.md
replace:
{
'@tanstack/react-query': '@tanstack/solid-query',
'useMutationState[(]': 'useMutationState(() => ',
'useMutation[(]': 'useMutation(() => ',
'useQuery[(]': 'useQuery(() => ',
'useQueries[(]': 'useQueries(() => ',
'useInfiniteQuery[(]': 'useInfiniteQuery(() => ',
}
---
[//]: # 'Example'
```tsx
const todosQuery = useQuery(() => ({
queryKey: ['todos'],
queryFn: () => fetch('/todos'),
initialData: initialTodos,
}))
```
[//]: # 'Example'
[//]: # 'Example2'
```tsx
// Will show initialTodos immediately, but also immediately refetch todos after mount
const todosQuery = useQuery(() => ({
queryKey: ['todos'],
queryFn: () => fetch('/todos'),
initialData: initialTodos,
}))
```
[//]: # 'Example2'
[//]: # 'Example3'
```tsx
// Show initialTodos immediately, but won't refetch until another interaction event is encountered after 1000 ms
const todosQuery = useQuery(() => ({
queryKey: ['todos'],
queryFn: () => fetch('/todos'),
initialData: initialTodos,
staleTime: 1000,
}))
```
[//]: # 'Example3'
[//]: # 'Example4'
```tsx
// Show initialTodos immediately, but won't refetch until another interaction event is encountered after 1000 ms
const todosQuery = useQuery(() => ({
queryKey: ['todos'],
queryFn: () => fetch('/todos'),
initialData: initialTodos,
staleTime: 60 * 1000, // 1 minute
// This could be 10 seconds ago or 10 minutes ago
initialDataUpdatedAt: initialTodosUpdatedTimestamp, // eg. 1608412420052
}))
```
[//]: # 'Example4'
[//]: # 'Example5'
```tsx
const todosQuery = useQuery(() => ({
queryKey: ['todos'],
queryFn: () => fetch('/todos'),
initialData: () => getExpensiveTodos(),
}))
```
[//]: # 'Example5'
[//]: # 'Example6'
```tsx
const todoQuery = useQuery(() => ({
queryKey: ['todo', todoId],
queryFn: () => fetch('/todos'),
initialData: () => {
// Use a todo from the 'todos' query as the initial data for this todo query
return queryClient.getQueryData(['todos'])?.find((d) => d.id === todoId)
},
}))
```
[//]: # 'Example6'
[//]: # 'Example7'
```tsx
const todoQuery = useQuery(() => ({
queryKey: ['todos', todoId],
queryFn: () => fetch(`/todos/${todoId}`),
initialData: () =>
queryClient.getQueryData(['todos'])?.find((d) => d.id === todoId),
initialDataUpdatedAt: () =>
queryClient.getQueryState(['todos'])?.dataUpdatedAt,
}))
```
[//]: # 'Example7'
[//]: # 'Example8'
```tsx
const todoQuery = useQuery(() => ({
queryKey: ['todo', todoId],
queryFn: () => fetch(`/todos/${todoId}`),
initialData: () => {
// Get the query state
const state = queryClient.getQueryState(['todos'])
// If the query exists and has data that is no older than 10 seconds...
if (state && Date.now() - state.dataUpdatedAt <= 10 * 1000) {
// return the individual todo
return state.data.find((d) => d.id === todoId)
}
// Otherwise, return undefined and let it fetch from a hard loading state!
},
}))
```
[//]: # 'Example8'

View File

@@ -0,0 +1,16 @@
---
id: invalidations-from-mutations
title: Invalidations from Mutations
ref: docs/framework/react/guides/invalidations-from-mutations.md
replace:
{
'React': 'Solid',
'@tanstack/react-query': '@tanstack/solid-query',
'useMutationState[(]': 'useMutationState(() => ',
'useMutation[(]': 'useMutation(() => ',
'useQuery[(]': 'useQuery(() => ',
'useQueries[(]': 'useQueries(() => ',
'useInfiniteQuery[(]': 'useInfiniteQuery(() => ',
'hook': 'function',
}
---

View File

@@ -0,0 +1,300 @@
---
id: mutations
title: Mutations
ref: docs/framework/react/guides/mutations.md
replace: { 'hook': 'function' }
---
[//]: # 'Example'
```tsx
import { Switch, Match, Show } from 'solid-js'
function App() {
const mutation = useMutation(() => ({
mutationFn: (newTodo) => {
return axios.post('/todos', newTodo)
},
}))
return (
<div>
<Switch>
<Match when={mutation.isPending}>Adding todo...</Match>
<Match when={true}>
<Show when={mutation.isError}>
<div>An error occurred: {mutation.error.message}</div>
</Show>
<Show when={mutation.isSuccess}>
<div>Todo added!</div>
</Show>
<button
onClick={() => {
mutation.mutate({ id: new Date(), title: 'Do Laundry' })
}}
>
Create Todo
</button>
</Match>
</Switch>
</div>
)
}
```
[//]: # 'Example'
[//]: # 'Info1'
[//]: # 'Info1'
[//]: # 'Example2'
```tsx
const CreateTodo = () => {
const mutation = useMutation(() => ({
mutationFn: (formData) => {
return fetch('/api', formData)
},
}))
const onSubmit = (event) => {
event.preventDefault()
mutation.mutate(new FormData(event.target))
}
return <form onSubmit={onSubmit}>...</form>
}
```
[//]: # 'Example2'
[//]: # 'Example3'
```tsx
import { createSignal, Show } from 'solid-js'
const CreateTodo = () => {
const [title, setTitle] = createSignal('')
const mutation = useMutation(() => ({ mutationFn: createTodo }))
const onCreateTodo = (e) => {
e.preventDefault()
mutation.mutate({ title: title() })
}
return (
<form onSubmit={onCreateTodo}>
<Show when={mutation.error}>
<h5 onClick={() => mutation.reset()}>{mutation.error}</h5>
</Show>
<input
type="text"
value={title()}
onInput={(e) => setTitle(e.currentTarget.value)}
/>
<br />
<button type="submit">Create Todo</button>
</form>
)
}
```
[//]: # 'Example3'
[//]: # 'Example4'
```tsx
useMutation(() => ({
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'
```tsx
useMutation(() => ({
mutationFn: addTodo,
onSuccess: async () => {
console.log("I'm first!")
},
onSettled: async () => {
console.log("I'm second!")
},
}))
```
[//]: # 'Example5'
[//]: # 'Example6'
```tsx
useMutation(() => ({
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
},
}))
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'
```tsx
useMutation(() => ({
mutationFn: addTodo,
onSuccess: (data, variables, onMutateResult, context) => {
// Will be called 3 times
},
}))
const todos = ['Todo 1', 'Todo 2', 'Todo 3']
todos.forEach((todo) => {
mutate(todo, {
onSuccess: (data, variables, onMutateResult, context) => {
// Will execute only once, for the last mutation (Todo 3),
// regardless which mutation resolves first
},
})
})
```
[//]: # 'Example7'
[//]: # 'Example8'
```tsx
const mutation = useMutation(() => ({ mutationFn: addTodo }))
try {
const todo = await mutation.mutateAsync(todo)
console.log(todo)
} catch (error) {
console.error(error)
} finally {
console.log('done')
}
```
[//]: # 'Example8'
[//]: # 'Example9'
```tsx
const mutation = useMutation(() => ({
mutationFn: addTodo,
retry: 3,
}))
```
[//]: # 'Example9'
[//]: # 'Example10'
```tsx
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 = useMutation(() => ({ 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'
### Persisting Offline mutations
If you persist offline mutations with the `persistQueryClient` plugin, mutations cannot be resumed when the page is reloaded unless you provide a default mutation function.
[//]: # 'PersistOfflineIntro'
[//]: # 'OfflineExampleLink'
[//]: # 'OfflineExampleLink'
[//]: # 'ExampleScopes'
```tsx
const mutation = useMutation(() => ({
mutationFn: addTodo,
scope: {
id: 'todo',
},
}))
```
[//]: # 'ExampleScopes'
[//]: # 'Materials'
## Further reading
For more information about mutations, have a look at [TkDodo's article on Mastering Mutations in TanStack Query](https://tkdodo.eu/blog/mastering-mutations-in-react-query).
[//]: # 'Materials'

View File

@@ -0,0 +1,5 @@
---
id: network-mode
title: Network Mode
ref: docs/framework/react/guides/network-mode.md
---

View File

@@ -0,0 +1,146 @@
---
id: optimistic-updates
title: Optimistic Updates
ref: docs/framework/react/guides/optimistic-updates.md
replace: { 'React Query': 'Solid Query', 'hook': 'function' }
---
[//]: # 'ExampleUI1'
```tsx
const addTodoMutation = useMutation(() => ({
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'
```tsx
<ul>
<For each={todoQuery.data}>{(todo) => <li>{todo.text}</li>}</For>
<Show when={addTodoMutation.isPending}>
<li style={{ opacity: 0.5 }}>{addTodoMutation.variables}</li>
</Show>
</ul>
```
[//]: # 'ExampleUI2'
[//]: # 'ExampleUI3'
```tsx
<Show when={addTodoMutation.isError}>
<li style={{ color: 'red' }}>
{addTodoMutation.variables}
<button onClick={() => addTodoMutation.mutate(addTodoMutation.variables)}>
Retry
</button>
</li>
</Show>
```
[//]: # 'ExampleUI3'
[//]: # 'ExampleUI4'
```tsx
// somewhere in your app
const mutation = useMutation(() => ({
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'
```tsx
const queryClient = useQueryClient()
useMutation(() => ({
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'
```tsx
useMutation(() => ({
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', newTodo.id] }),
}))
```
[//]: # 'Example2'
[//]: # 'Example3'
```tsx
useMutation(() => ({
mutationFn: updateTodo,
// ...
onSettled: async (newTodo, error, variables, onMutateResult, context) => {
if (error) {
// do something
}
},
}))
```
[//]: # 'Example3'

View File

@@ -0,0 +1,81 @@
---
id: paginated-queries
title: Paginated / Lagged Queries
ref: docs/framework/react/guides/paginated-queries.md
replace: { 'hook': 'function' }
---
[//]: # 'Example'
```tsx
const projectsQuery = useQuery(() => ({
queryKey: ['projects', page()],
queryFn: () => fetchProjects(page()),
}))
```
[//]: # 'Example'
[//]: # 'Example2'
```tsx
import { createSignal, Switch, Match, Show, For } from 'solid-js'
import { keepPreviousData, useQuery } from '@tanstack/solid-query'
function Todos() {
const [page, setPage] = createSignal(0)
const fetchProjects = (page = 0) =>
fetch('/api/projects?page=' + page).then((res) => res.json())
const projectsQuery = useQuery(() => ({
queryKey: ['projects', page()],
queryFn: () => fetchProjects(page()),
placeholderData: keepPreviousData,
}))
return (
<div>
<Switch>
<Match when={projectsQuery.isPending}>
<div>Loading...</div>
</Match>
<Match when={projectsQuery.isError}>
<div>Error: {projectsQuery.error.message}</div>
</Match>
<Match when={projectsQuery.isSuccess}>
<div>
<For each={projectsQuery.data.projects}>
{(project) => <p>{project.name}</p>}
</For>
</div>
</Match>
</Switch>
<span>Current Page: {page() + 1}</span>
<button
onClick={() => setPage((old) => Math.max(old - 1, 0))}
disabled={page() === 0}
>
Previous Page
</button>
<button
onClick={() => {
if (!projectsQuery.isPlaceholderData && projectsQuery.data?.hasMore) {
setPage((old) => old + 1)
}
}}
// Disable the Next Page button until we know a next page is available
disabled={
projectsQuery.isPlaceholderData || !projectsQuery.data?.hasMore
}
>
Next Page
</button>
<Show when={projectsQuery.isFetching}>
<span> Loading...</span>
</Show>
</div>
)
}
```
[//]: # 'Example2'

View File

@@ -0,0 +1,57 @@
---
id: parallel-queries
title: Parallel Queries
ref: docs/framework/react/guides/parallel-queries.md
replace:
{
'@tanstack/react-query': '@tanstack/solid-query',
'useMutationState[(]': 'useMutationState(() => ',
'useMutation[(]': 'useMutation(() => ',
'useQuery[(]': 'useQuery(() => ',
'useQueries[(]': 'useQueries(() => ',
'useInfiniteQuery[(]': 'useInfiniteQuery(() => ',
'hooks': 'functions',
}
---
[//]: # 'Example'
```tsx
function App () {
// The following queries will execute in parallel
const usersQuery = useQuery(() => ({ queryKey: ['users'], queryFn: fetchUsers }))
const teamsQuery = useQuery(() => ({ queryKey: ['teams'], queryFn: fetchTeams }))
const projectsQuery = useQuery(() => ({ queryKey: ['projects'], queryFn: fetchProjects }))
...
}
```
[//]: # 'Example'
[//]: # 'Info'
[//]: # 'Info'
[//]: # 'DynamicParallelIntro'
If the number of queries you need to execute changes, you cannot use manual querying since that would break reactivity. Instead, TanStack Query provides a `useQueries` function, which you can use to dynamically execute as many queries in parallel as you'd like.
[//]: # 'DynamicParallelIntro'
[//]: # 'DynamicParallelDescription'
`useQueries` accepts an **accessor that returns an options object** with a **queries key** whose value is an **array of query objects**. It returns an **array of query results**:
[//]: # 'DynamicParallelDescription'
[//]: # 'Example2'
```tsx
function App(props) {
const userQueries = useQueries(() => ({
queries: props.users.map((user) => {
return {
queryKey: ['user', user.id],
queryFn: () => fetchUserById(user.id),
}
}),
}))
}
```
[//]: # 'Example2'

View File

@@ -0,0 +1,68 @@
---
id: placeholder-query-data
title: Placeholder Query Data
ref: docs/framework/react/guides/placeholder-query-data.md
---
[//]: # 'ExampleValue'
```tsx
function Todos() {
const todosQuery = useQuery(() => ({
queryKey: ['todos'],
queryFn: () => fetch('/todos'),
placeholderData: placeholderTodos,
}))
}
```
[//]: # 'ExampleValue'
[//]: # 'Memoization'
### Placeholder Data Memoization
If the process for accessing a query's placeholder data is intensive or just not something you want to perform on every render, you can memoize the value:
```tsx
function Todos() {
const placeholderData = createMemo(() => generateFakeTodos())
const todosQuery = useQuery(() => ({
queryKey: ['todos'],
queryFn: () => fetch('/todos'),
placeholderData: placeholderData(),
}))
}
```
[//]: # 'Memoization'
[//]: # 'ExampleFunction'
```tsx
const todosQuery = useQuery(() => ({
queryKey: ['todos', id],
queryFn: () => fetch(`/todos/${id}`),
placeholderData: (previousData, previousQuery) => previousData,
}))
```
[//]: # 'ExampleFunction'
[//]: # 'ExampleCache'
```tsx
function BlogPost(props) {
const queryClient = useQueryClient()
const blogPostQuery = useQuery(() => ({
queryKey: ['blogPost', props.blogPostId],
queryFn: () => fetch(`/blogPosts/${props.blogPostId}`),
placeholderData: () => {
// Use the smaller/preview version of the blogPost from the 'blogPosts'
// query as the placeholder data for this blogPost query
return queryClient
.getQueryData(['blogPosts'])
?.find((d) => d.id === props.blogPostId)
},
}))
}
```
[//]: # 'ExampleCache'

View File

@@ -0,0 +1,73 @@
---
id: polling
title: Polling
ref: docs/framework/react/guides/polling.md
replace: { '@tanstack/react-query': '@tanstack/solid-query' }
---
[//]: # 'Example1'
```tsx
useQuery(() => ({
queryKey: ['prices'],
queryFn: fetchPrices,
refetchInterval: 5_000, // every 5 seconds
}))
```
[//]: # 'Example1'
[//]: # 'Example2'
```tsx
useQuery(() => ({
queryKey: ['job', jobId],
queryFn: () => fetchJobStatus(jobId),
refetchInterval: (query) => {
// Stop polling once the job finishes
if (query.state.data?.status === 'complete') return false
return 2_000
},
}))
```
[//]: # 'Example2'
[//]: # 'Example3'
```tsx
useQuery(() => ({
queryKey: ['portfolio'],
queryFn: fetchPortfolio,
refetchInterval: 30_000,
refetchIntervalInBackground: true,
}))
```
[//]: # 'Example3'
[//]: # 'Example4'
```tsx
useQuery(() => ({
queryKey: ['prices', tokenAddress],
queryFn: () => fetchPrice(tokenAddress),
refetchInterval: () => {
if (!tokenAddress || isPaused) return false
return 15_000
},
}))
```
[//]: # 'Example4'
[//]: # 'Example5'
```tsx
useQuery(() => ({
queryKey: ['chainStatus'],
queryFn: fetchChainStatus,
refetchInterval: 10_000,
networkMode: 'always',
}))
```
[//]: # 'Example5'
[//]: # 'ReactNative'
[//]: # 'ReactNative'

View File

@@ -0,0 +1,272 @@
---
id: prefetching
title: Prefetching & Router Integration
ref: docs/framework/react/guides/prefetching.md
replace: { 'react-query': 'solid-query', 'React': 'Solid' }
---
[//]: # 'ExampleComponent'
```tsx
import { Switch, Match } from 'solid-js'
function Article(props) {
const articleQuery = useQuery(() => ({
queryKey: ['article', props.id],
queryFn: getArticleById,
}))
return (
<Switch>
<Match when={articleQuery.isPending}>
Loading article...
</Match>
<Match when={articleQuery.isSuccess}>
<ArticleHeader articleData={articleQuery.data} />
<ArticleBody articleData={articleQuery.data} />
<Comments id={props.id} />
</Match>
</Switch>
)
}
function Comments(props) {
const commentsQuery = useQuery(() => ({
queryKey: ['article-comments', props.id],
queryFn: getArticleCommentsById,
}))
...
}
```
[//]: # 'ExampleComponent'
[//]: # 'ExampleParentComponent'
```tsx
import { Switch, Match } from 'solid-js'
function Article(props) {
const articleQuery = useQuery(() => ({
queryKey: ['article', props.id],
queryFn: getArticleById,
}))
// Prefetch
useQuery(() => ({
queryKey: ['article-comments', props.id],
queryFn: getArticleCommentsById,
// Optional optimization to avoid rerenders when this query changes:
notifyOnChangeProps: [],
}))
return (
<Switch>
<Match when={articleQuery.isPending}>
Loading article...
</Match>
<Match when={articleQuery.isSuccess}>
<ArticleHeader articleData={articleQuery.data} />
<ArticleBody articleData={articleQuery.data} />
<Comments id={props.id} />
</Match>
</Switch>
)
}
function Comments(props) {
const commentsQuery = useQuery(() => ({
queryKey: ['article-comments', props.id],
queryFn: getArticleCommentsById,
}))
...
}
```
[//]: # 'ExampleParentComponent'
[//]: # 'Suspense'
Another way is to prefetch inside the query function. This makes sense if you know that every time an article is fetched it's very likely comments will also be needed. For this, we'll use `queryClient.query`:
```tsx
const queryClient = useQueryClient()
const articleQuery = useQuery(() => ({
queryKey: ['article', id],
queryFn: (...args) => {
void queryClient
.query({
queryKey: ['article-comments', id],
queryFn: getArticleCommentsById,
})
.catch(noop)
return getArticleById(...args)
},
}))
```
Prefetching in an effect also works:
```tsx
import { createEffect } from 'solid-js'
const queryClient = useQueryClient()
createEffect(() => {
void queryClient
.query({
queryKey: ['article-comments', id],
queryFn: getArticleCommentsById,
})
.catch(noop)
})
```
To recap, if you want to prefetch a query during the component lifecycle, there are a few different ways to do it, pick the one that suits your situation best:
- Use `useQuery` and ignore the result
- Prefetch inside the query function
- Prefetch in an effect
Let's look at a slightly more advanced case next.
[//]: # 'Suspense'
[//]: # 'ExampleConditionally1'
```tsx
import { lazy, Switch, Match, For } from 'solid-js'
// This lazy loads the GraphFeedItem component, meaning
// it wont start loading until something renders it
const GraphFeedItem = lazy(() => import('./GraphFeedItem'))
function Feed() {
const feedQuery = useQuery(() => ({
queryKey: ['feed'],
queryFn: getFeed,
}))
return (
<Switch>
<Match when={feedQuery.isPending}>
Loading feed...
</Match>
<Match when={feedQuery.isSuccess}>
<For each={feedQuery.data}>
{(feedItem) => {
if (feedItem.type === 'GRAPH') {
return <GraphFeedItem feedItem={feedItem} />
}
return <StandardFeedItem feedItem={feedItem} />
}}
</For>
</Match>
</Switch>
)
}
// GraphFeedItem.tsx
function GraphFeedItem(props) {
const graphQuery = useQuery(() => ({
queryKey: ['graph', props.feedItem.id],
queryFn: getGraphDataById,
}))
...
}
```
[//]: # 'ExampleConditionally1'
[//]: # 'ExampleConditionally2'
```tsx
function Feed() {
const queryClient = useQueryClient()
const feedQuery = useQuery(() => ({
queryKey: ['feed'],
queryFn: async (...args) => {
const feed = await getFeed(...args)
for (const feedItem of feed) {
if (feedItem.type === 'GRAPH') {
void queryClient.query({
queryKey: ['graph', feedItem.id],
queryFn: getGraphDataById,
}).catch(noop)
}
}
return feed
}
}))
...
}
```
[//]: # 'ExampleConditionally2'
[//]: # 'Router'
## Router Integration
Because data fetching in the component tree itself can easily lead to request waterfalls and the different fixes for that can be cumbersome as they accumulate throughout the application, an attractive way to do prefetching is integrating it at the router level.
In this approach, you explicitly declare for each _route_ what data is going to be needed for that component tree, ahead of time. Because Server Rendering has traditionally needed all data to be loaded before rendering starts, this has been the dominating approach for SSR'd apps for a long time. This is still a common approach and you can read more about it in the [Server Rendering & Hydration guide](./ssr.md).
For now, let's focus on the client side case and look at an example of how you can make this work with [TanStack Router](https://tanstack.com/router). These examples leave out a lot of setup and boilerplate to stay concise, you can check out a [full Solid Query example](https://tanstack.com/router/latest/docs/framework/solid/examples/basic-solid-query-file-based) over in the [TanStack Router docs](https://tanstack.com/router/latest/docs).
When integrating at the router level, you can choose to either _block_ rendering of that route until all data is present, or you can start a prefetch but not await the result. That way, you can start rendering the route as soon as possible. You can also mix these two approaches and await some critical data, but start rendering before all the secondary data has finished loading. In this example, we'll configure an `/article` route to not render until the article data has finished loading, as well as start prefetching comments as soon as possible, but not block rendering the route if comments haven't finished loading yet.
Note that many route loaders use error boundaries to trigger error fallbacks. Whereas up to now we have been using `.catch(noop)` to ignore errors for data that will be retried by `useQuery`, for critical data that the route will not work without, you should `await` the promise without `noop` and handle the error in a `try` block or the router's error handling (such as TanStack Router's `errorComponent`).
```tsx
const queryClient = new QueryClient()
const routerContext = new RouterContext()
const rootRoute = routerContext.createRootRoute({
component: () => { ... }
})
const articleRoute = new Route({
getParentRoute: () => rootRoute,
path: 'article',
beforeLoad: () => {
return {
articleQueryOptions: { queryKey: ['article'], queryFn: fetchArticle },
commentsQueryOptions: { queryKey: ['comments'], queryFn: fetchComments },
}
},
loader: async ({
context: { queryClient },
routeContext: { articleQueryOptions, commentsQueryOptions },
}) => {
// Fetch comments asap, but don't block or throw errors
void queryClient.query(commentsQueryOptions).catch(noop)
// Don't render the route at all until article has been fetched
// As this is critical data we want the error component to trigger
// as soon as possible if something goes wrong
await queryClient.query({
...articleQueryOptions,
// If we have the article loaded already, we don't want to block on
// an extra prefetch; fallback on the default useQuery behavior to
// keep the data fresh
staleTime: 'static'
})
},
component: ({ useRouteContext }) => {
const { articleQueryOptions, commentsQueryOptions } = useRouteContext()
const articleQuery = useQuery(() => articleQueryOptions)
const commentsQuery = useQuery(() => commentsQueryOptions)
return (
...
)
},
errorComponent: () => 'Oh crap!',
})
```
Integration with other routers is also possible.
[//]: # 'Router'

View File

@@ -0,0 +1,97 @@
---
id: queries
title: Queries
ref: docs/framework/react/guides/queries.md
---
[//]: # 'SubscribeDescription'
To subscribe to a query in your components, call the `useQuery` function with at least:
[//]: # 'SubscribeDescription'
[//]: # 'Example'
```tsx
import { useQuery } from '@tanstack/solid-query'
function App() {
const todosQuery = useQuery(() => ({
queryKey: ['todos'],
queryFn: fetchTodoList,
}))
}
```
[//]: # 'Example'
[//]: # 'Example2'
```tsx
const todosQuery = useQuery(() => ({
queryKey: ['todos'],
queryFn: fetchTodoList,
}))
```
[//]: # 'Example2'
[//]: # 'Example3'
```tsx
import { Switch, Match, For } from 'solid-js'
function Todos() {
const todosQuery = useQuery(() => ({
queryKey: ['todos'],
queryFn: fetchTodoList,
}))
return (
<Switch>
<Match when={todosQuery.isPending}>
<span>Loading...</span>
</Match>
<Match when={todosQuery.isError}>
<span>Error: {todosQuery.error.message}</span>
</Match>
<Match when={todosQuery.isSuccess}>
<ul>
<For each={todosQuery.data}>{(todo) => <li>{todo.title}</li>}</For>
</ul>
</Match>
</Switch>
)
}
```
[//]: # 'Example3'
[//]: # 'Example4'
```tsx
import { Switch, Match, For } from 'solid-js'
function Todos() {
const todosQuery = useQuery(() => ({
queryKey: ['todos'],
queryFn: fetchTodoList,
}))
return (
<Switch>
<Match when={todosQuery.status === 'pending'}>
<span>Loading...</span>
</Match>
<Match when={todosQuery.status === 'error'}>
<span>Error: {todosQuery.error.message}</span>
</Match>
<Match when={todosQuery.status === 'success'}>
<ul>
<For each={todosQuery.data}>{(todo) => <li>{todo.title}</li>}</For>
</ul>
</Match>
</Switch>
)
}
```
[//]: # 'Example4'
[//]: # 'Materials'
[//]: # 'Materials'

View File

@@ -0,0 +1,165 @@
---
id: query-cancellation
title: Query Cancellation
ref: docs/framework/react/guides/query-cancellation.md
replace:
{
'@tanstack/react-query': '@tanstack/solid-query',
'useMutationState[(]': 'useMutationState(() => ',
'useMutation[(]': 'useMutation(() => ',
'useQuery[(]': 'useQuery(() => ',
'useQueries[(]': 'useQueries(() => ',
'useInfiniteQuery[(]': 'useInfiniteQuery(() => ',
}
---
[//]: # 'Example'
```tsx
const todosQuery = useQuery(() => ({
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'
```tsx
import axios from 'axios'
const todosQuery = useQuery(() => ({
queryKey: ['todos'],
queryFn: ({ signal }) =>
axios.get('/todos', {
// Pass the signal to `axios`
signal,
}),
}))
```
[//]: # 'Example2'
[//]: # 'Example3'
```tsx
import axios from 'axios'
const todosQuery = useQuery(() => ({
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'
```tsx
const todosQuery = useQuery(() => ({
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'
```tsx
const client = new GraphQLClient(endpoint)
const todosQuery = useQuery(() => ({
queryKey: ['todos'],
queryFn: ({ signal }) => {
client.request({ document: query, signal })
},
}))
```
[//]: # 'Example5'
[//]: # 'Example6'
```tsx
const todosQuery = useQuery(() => ({
queryKey: ['todos'],
queryFn: ({ signal }) => {
const client = new GraphQLClient(endpoint, {
signal,
})
return client.request(query, variables)
},
}))
```
[//]: # 'Example6'
[//]: # 'Example7'
```tsx
const todosQuery = useQuery(() => ({
queryKey: ['todos'],
queryFn: async ({ signal }) => {
const resp = await fetch('/todos', { signal })
return resp.json()
},
}))
const queryClient = useQueryClient()
return (
<button
onClick={(e) => {
e.preventDefault()
queryClient.cancelQueries({ queryKey: ['todos'] })
}}
>
Cancel
</button>
)
```
[//]: # 'Example7'
[//]: # 'Limitations'
[//]: # 'Limitations'

View File

@@ -0,0 +1,81 @@
---
id: query-functions
title: Query Functions
ref: docs/framework/react/guides/query-functions.md
---
[//]: # 'Example'
```tsx
useQuery(() => ({ queryKey: ['todos'], queryFn: fetchAllTodos }))
useQuery(() => ({
queryKey: ['todos', todoId],
queryFn: () => fetchTodoById(todoId),
}))
useQuery(() => ({
queryKey: ['todos', todoId],
queryFn: async () => {
const data = await fetchTodoById(todoId)
return data
},
}))
useQuery(() => ({
queryKey: ['todos', todoId],
queryFn: ({ queryKey }) => fetchTodoById(queryKey[1]),
}))
```
[//]: # 'Example'
[//]: # 'Example2'
```tsx
const todosQuery = useQuery(() => ({
queryKey: ['todos', todoId],
queryFn: async () => {
if (somethingGoesWrong) {
throw new Error('Oh no!')
}
if (somethingElseGoesWrong) {
return Promise.reject(new Error('Oh no!'))
}
return data
},
}))
```
[//]: # 'Example2'
[//]: # 'Example3'
```tsx
useQuery(() => ({
queryKey: ['todos', todoId],
queryFn: async () => {
const response = await fetch('/todos/' + todoId)
if (!response.ok) {
throw new Error('Network response was not ok')
}
return response.json()
},
}))
```
[//]: # 'Example3'
[//]: # 'Example4'
```tsx
function Todos(props) {
const todosQuery = useQuery(() => ({
queryKey: ['todos', { status: props.status, page: props.page }],
queryFn: fetchTodoList,
}))
}
// Access the key, status and page variables in your query function!
function fetchTodoList({ queryKey }) {
const [_key, { status, page }] = queryKey
return new Promise()
}
```
[//]: # 'Example4'

View File

@@ -0,0 +1,15 @@
---
id: query-invalidation
title: Query Invalidation
ref: docs/framework/react/guides/query-invalidation.md
replace:
{
'@tanstack/react-query': '@tanstack/solid-query',
'useMutationState[(]': 'useMutationState(() => ',
'useMutation[(]': 'useMutation(() => ',
'useQuery[(]': 'useQuery(() => ',
'useQueries[(]': 'useQueries(() => ',
'useInfiniteQuery[(]': 'useInfiniteQuery(() => ',
'hooks': 'functions',
}
---

View File

@@ -0,0 +1,71 @@
---
id: query-keys
title: Query Keys
ref: docs/framework/react/guides/query-keys.md
replace:
{
'@tanstack/react-query': '@tanstack/solid-query',
'useMutationState[(]': 'useMutationState(() => ',
'useMutation[(]': 'useMutation(() => ',
'useQuery[(]': 'useQuery(() => ',
'useQueries[(]': 'useQueries(() => ',
'useInfiniteQuery[(]': 'useInfiniteQuery(() => ',
'React Query Keys': 'TanStack Query Keys',
}
---
[//]: # 'Example'
```tsx
// A list of todos
useQuery(() => ({ queryKey: ['todos'], ... }))
// Something else, whatever!
useQuery(() => ({ queryKey: ['something', 'special'], ... }))
```
[//]: # 'Example'
[//]: # 'Example2'
```tsx
// An individual todo
useQuery(() => ({ queryKey: ['todo', 5], ... }))
// An individual todo in a "preview" format
useQuery(() => ({ queryKey: ['todo', 5, { preview: true }], ...}))
// A list of todos that are "done"
useQuery(() => ({ queryKey: ['todos', { type: 'done' }], ... }))
```
[//]: # 'Example2'
[//]: # 'Example3'
```tsx
useQuery(() => ({ queryKey: ['todos', { status, page }], ... }))
useQuery(() => ({ queryKey: ['todos', { page, status }], ...}))
useQuery(() => ({ queryKey: ['todos', { page, status, other: undefined }], ... }))
```
[//]: # 'Example3'
[//]: # 'Example4'
```tsx
useQuery(() => ({ queryKey: ['todos', status, page], ... }))
useQuery(() => ({ queryKey: ['todos', page, status], ...}))
useQuery(() => ({ queryKey: ['todos', undefined, page, status], ...}))
```
[//]: # 'Example4'
[//]: # 'Example5'
```tsx
function Todos(props) {
const todosQuery = useQuery(() => ({
queryKey: ['todos', props.todoId],
queryFn: () => fetchTodoById(props.todoId),
}))
}
```
[//]: # 'Example5'

View File

@@ -0,0 +1,56 @@
---
id: query-options
title: Query Options
ref: docs/framework/react/guides/query-options.md
replace:
{
'@tanstack/react-query': '@tanstack/solid-query',
'useMutationState[(]': 'useMutationState(() => ',
'useMutation[(]': 'useMutation(() => ',
'useQuery[(]': 'useQuery(() => ',
'useQueries[(]': 'useQueries(() => ',
'useInfiniteQuery[(]': 'useInfiniteQuery(() => ',
}
---
[//]: # 'Example1'
```ts
import { queryOptions } from '@tanstack/solid-query'
function groupOptions(id: number) {
return queryOptions({
queryKey: ['groups', id],
queryFn: () => fetchGroups(id),
staleTime: 5 * 1000,
})
}
// usage:
useQuery(() => groupOptions(1))
useQueries(() => ({
queries: [groupOptions(1), groupOptions(2)],
}))
queryClient.query(groupOptions(23))
queryClient.setQueryData(groupOptions(42).queryKey, newGroups)
```
[//]: # 'Example1'
[//]: # 'SelectDescription'
You can still override some options at the component level. A very common and useful pattern is to create per-component `select` functions:
[//]: # 'SelectDescription'
[//]: # 'Example2'
```ts
// Type inference still works, so query.data will be the return type of select instead of queryFn
const groupQuery = useQuery(() => ({
...groupOptions(1),
select: (data) => data.groupName,
}))
```
[//]: # 'Example2'

View File

@@ -0,0 +1,14 @@
---
id: query-retries
title: Query Retries
ref: docs/framework/react/guides/query-retries.md
replace:
{
'@tanstack/react-query': '@tanstack/solid-query',
'useMutationState[(]': 'useMutationState(() => ',
'useMutation[(]': 'useMutation(() => ',
'useQuery[(]': 'useQuery(() => ',
'useQueries[(]': 'useQueries(() => ',
'useInfiniteQuery[(]': 'useInfiniteQuery(() => ',
}
---

View File

@@ -0,0 +1,209 @@
---
id: request-waterfalls
title: Performance & Request Waterfalls
ref: docs/framework/react/guides/request-waterfalls.md
replace: { 'React Query': 'Solid Query' }
---
[//]: # 'AdvancedSSRLink'
[//]: # 'AdvancedSSRLink'
[//]: # 'DependentExample'
```tsx
// Get the user
const userQuery = useQuery(() => ({
queryKey: ['user', email],
queryFn: getUserByEmail,
}))
const userId = () => userQuery.data?.id
// Then get the user's projects
const projectsQuery = useQuery(() => ({
queryKey: ['projects', userId()],
queryFn: getProjectsByUser,
// The query will not execute until the userId exists
enabled: !!userId(),
}))
```
[//]: # 'DependentExample'
[//]: # 'ServerComponentsNote1'
[//]: # 'ServerComponentsNote1'
[//]: # 'SuspenseSerial'
[//]: # 'SuspenseSerial'
[//]: # 'NestedIntro'
Nested Component Waterfalls is when both a parent and a child component contains queries, and the parent does not render the child until its query is done.
[//]: # 'NestedIntro'
[//]: # 'NestedExample'
```tsx
import { Switch, Match } from 'solid-js'
function Article(props) {
const articleQuery = useQuery(() => ({
queryKey: ['article', props.id],
queryFn: getArticleById,
}))
return (
<Switch>
<Match when={articleQuery.isPending}>
Loading article...
</Match>
<Match when={articleQuery.isSuccess}>
<ArticleHeader articleData={articleQuery.data} />
<ArticleBody articleData={articleQuery.data} />
<Comments id={props.id} />
</Match>
</Switch>
)
}
function Comments(props) {
const commentsQuery = useQuery(() => ({
queryKey: ['article-comments', props.id],
queryFn: getArticleCommentsById,
}))
...
}
```
[//]: # 'NestedExample'
[//]: # 'NestedHoistedExample'
```tsx
import { Switch, Match, Show } from 'solid-js'
function Article(props) {
const articleQuery = useQuery(() => ({
queryKey: ['article', props.id],
queryFn: getArticleById,
}))
const commentsQuery = useQuery(() => ({
queryKey: ['article-comments', props.id],
queryFn: getArticleCommentsById,
}))
return (
<Switch>
<Match when={articleQuery.isPending}>Loading article...</Match>
<Match when={articleQuery.isSuccess}>
<ArticleHeader articleData={articleQuery.data} />
<ArticleBody articleData={articleQuery.data} />
<Switch>
<Match when={commentsQuery.isPending}>Loading comments...</Match>
<Match when={commentsQuery.isSuccess}>
<Comments commentsData={commentsQuery.data} />
</Match>
</Switch>
</Match>
</Switch>
)
}
```
[//]: # 'NestedHoistedExample'
[//]: # 'NestedHoistedOutro'
The two queries will now fetch in parallel.
[//]: # 'NestedHoistedOutro'
[//]: # 'DependentNestedExample'
```tsx
import { Switch, Match, For } from 'solid-js'
function Feed() {
const feedQuery = useQuery(() => ({
queryKey: ['feed'],
queryFn: getFeed,
}))
return (
<Switch>
<Match when={feedQuery.isPending}>
Loading feed...
</Match>
<Match when={feedQuery.isSuccess}>
<For each={feedQuery.data}>
{(feedItem) => {
if (feedItem.type === 'GRAPH') {
return <GraphFeedItem feedItem={feedItem} />
}
return <StandardFeedItem feedItem={feedItem} />
}}
</For>
</Match>
</Switch>
)
}
function GraphFeedItem(props) {
const graphQuery = useQuery(() => ({
queryKey: ['graph', props.feedItem.id],
queryFn: getGraphDataById,
}))
...
}
```
[//]: # 'DependentNestedExample'
[//]: # 'ServerComponentsNote2'
In this example, we can't trivially flatten the waterfall by just hoisting the query to the parent, or even adding prefetching. Just like the dependent query example at the beginning of this guide, one option is to refactor our API to include the graph data in the `getFeed` query.
[//]: # 'ServerComponentsNote2'
[//]: # 'LazyExample'
```tsx
import { lazy, Switch, Match, For } from 'solid-js'
// This lazy loads the GraphFeedItem component, meaning
// it wont start loading until something renders it
const GraphFeedItem = lazy(() => import('./GraphFeedItem'))
function Feed() {
const feedQuery = useQuery(() => ({
queryKey: ['feed'],
queryFn: getFeed,
}))
return (
<Switch>
<Match when={feedQuery.isPending}>
Loading feed...
</Match>
<Match when={feedQuery.isSuccess}>
<For each={feedQuery.data}>
{(feedItem) => {
if (feedItem.type === 'GRAPH') {
return <GraphFeedItem feedItem={feedItem} />
}
return <StandardFeedItem feedItem={feedItem} />
}}
</For>
</Match>
</Switch>
)
}
// GraphFeedItem.tsx
function GraphFeedItem(props) {
const graphQuery = useQuery(() => ({
queryKey: ['graph', props.feedItem.id],
queryFn: getGraphDataById,
}))
...
}
```
[//]: # 'LazyExample'
[//]: # 'ServerComponentsNote3'
[//]: # 'ServerComponentsNote3'

View File

@@ -0,0 +1,6 @@
---
id: scroll-restoration
title: Scroll Restoration
ref: docs/framework/react/guides/scroll-restoration.md
replace: { 'React Router’s ScrollRestoration, ': '' }
---

View File

@@ -0,0 +1,6 @@
---
id: ssr
title: SSR
---
Will come soon

View File

@@ -0,0 +1,41 @@
---
id: suspense
title: Suspense
---
Solid Query can also be used with Solid's [Suspense](https://docs.solidjs.com/reference/components/suspense) API's.
To do that you need to wrap your suspendable component with `Suspense` component provided by Solid
```tsx
import { Suspense } from 'solid-js'
;<Suspense fallback={<LoadingSpinner />}>
<SuspendableComponent />
</Suspense>
```
You can use async `suspense` function that is provided by `solid-query`.
```tsx
import { useQuery } from '@tanstack/solid-query'
const todoFetcher = async () =>
await fetch('https://jsonplaceholder.cypress.io/todos').then((response) =>
response.json(),
)
function SuspendableComponent() {
const todosQuery = useQuery(() => ({
queryKey: ['todos'],
queryFn: todoFetcher,
}))
// Accessing todosQuery.data directly inside a <Suspense> boundary
// automatically triggers suspension until data is ready
return <div>Data: {JSON.stringify(todosQuery.data)}</div>
}
```
## Fetch-on-render vs Render-as-you-fetch
Out of the box, Solid Query in `suspense` mode works really well as a **Fetch-on-render** solution with no additional configuration. This means that when your components attempt to mount, they will trigger query fetching and suspend, but only once you have imported them and mounted them. If you want to take it to the next level and implement a **Render-as-you-fetch** model, we recommend implementing [Prefetching](./prefetching) on routing callbacks and/or user interactions events to start loading queries before they are mounted and hopefully even before you start importing or mounting their parent components.

View File

@@ -0,0 +1,4 @@
---
id: testing
title: Testing
---

View File

@@ -0,0 +1,50 @@
---
id: updates-from-mutation-responses
title: Updates from Mutation Responses
ref: docs/framework/react/guides/updates-from-mutation-responses.md
replace: { 'hook': 'function' }
---
[//]: # 'Example'
```tsx
const queryClient = useQueryClient()
const mutation = useMutation(() => ({
mutationFn: editTodo,
onSuccess: (data) => {
queryClient.setQueryData(['todo', { id: 5 }], data)
},
}))
mutation.mutate({
id: 5,
name: 'Do the laundry',
})
// The query below will be updated with the response from the
// successful mutation
const todoQuery = useQuery(() => ({
queryKey: ['todo', { id: 5 }],
queryFn: fetchTodoById,
}))
```
[//]: # 'Example'
[//]: # 'Example2'
```tsx
const useMutateTodo = () => {
const queryClient = useQueryClient()
return useMutation(() => ({
mutationFn: editTodo,
// Notice the second argument is the variables object that the `mutate` function receives
onSuccess: (data, variables) => {
queryClient.setQueryData(['todo', { id: variables.id }], data)
},
}))
}
```
[//]: # 'Example2'

View File

@@ -0,0 +1,20 @@
---
id: window-focus-refetching
title: Window Focus Refetching
ref: docs/framework/react/guides/window-focus-refetching.md
replace: { '@tanstack/react-query': '@tanstack/solid-query' }
---
[//]: # 'Example2'
```tsx
useQuery(() => ({
queryKey: ['todos'],
queryFn: fetchTodos,
refetchOnWindowFocus: false,
}))
```
[//]: # 'Example2'
[//]: # 'ReactNative'
[//]: # 'ReactNative'