snapshot of https://github.com/TanStack/query.git (main, 2026-09-16; blobs > 131072B omitted)
This commit is contained in:
6
docs/framework/solid/guides/advanced-ssr.md
Normal file
6
docs/framework/solid/guides/advanced-ssr.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
id: advanced-ssr
|
||||
title: Advanced Server Rendering
|
||||
---
|
||||
|
||||
Will come soon
|
||||
@@ -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'
|
||||
10
docs/framework/solid/guides/caching.md
Normal file
10
docs/framework/solid/guides/caching.md
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
id: caching
|
||||
title: Caching Examples
|
||||
ref: docs/framework/react/guides/caching.md
|
||||
replace:
|
||||
{
|
||||
'useQuery[(][{] queryKey': 'useQuery(() => ({ queryKey',
|
||||
'fetchTodos [}][)]': 'fetchTodos }))',
|
||||
}
|
||||
---
|
||||
53
docs/framework/solid/guides/default-query-function.md
Normal file
53
docs/framework/solid/guides/default-query-function.md
Normal 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'
|
||||
51
docs/framework/solid/guides/dependent-queries.md
Normal file
51
docs/framework/solid/guides/dependent-queries.md
Normal 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'
|
||||
102
docs/framework/solid/guides/disabling-queries.md
Normal file
102
docs/framework/solid/guides/disabling-queries.md
Normal 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'
|
||||
@@ -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' }
|
||||
---
|
||||
5
docs/framework/solid/guides/filters.md
Normal file
5
docs/framework/solid/guides/filters.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
id: filters
|
||||
title: Filters
|
||||
ref: docs/framework/react/guides/filters.md
|
||||
---
|
||||
13
docs/framework/solid/guides/important-defaults.md
Normal file
13
docs/framework/solid/guides/important-defaults.md
Normal 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'
|
||||
140
docs/framework/solid/guides/infinite-queries.md
Normal file
140
docs/framework/solid/guides/infinite-queries.md
Normal 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'
|
||||
127
docs/framework/solid/guides/initial-query-data.md
Normal file
127
docs/framework/solid/guides/initial-query-data.md
Normal 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'
|
||||
16
docs/framework/solid/guides/invalidations-from-mutations.md
Normal file
16
docs/framework/solid/guides/invalidations-from-mutations.md
Normal 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',
|
||||
}
|
||||
---
|
||||
300
docs/framework/solid/guides/mutations.md
Normal file
300
docs/framework/solid/guides/mutations.md
Normal 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'
|
||||
5
docs/framework/solid/guides/network-mode.md
Normal file
5
docs/framework/solid/guides/network-mode.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
id: network-mode
|
||||
title: Network Mode
|
||||
ref: docs/framework/react/guides/network-mode.md
|
||||
---
|
||||
146
docs/framework/solid/guides/optimistic-updates.md
Normal file
146
docs/framework/solid/guides/optimistic-updates.md
Normal 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'
|
||||
81
docs/framework/solid/guides/paginated-queries.md
Normal file
81
docs/framework/solid/guides/paginated-queries.md
Normal 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'
|
||||
57
docs/framework/solid/guides/parallel-queries.md
Normal file
57
docs/framework/solid/guides/parallel-queries.md
Normal 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'
|
||||
68
docs/framework/solid/guides/placeholder-query-data.md
Normal file
68
docs/framework/solid/guides/placeholder-query-data.md
Normal 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'
|
||||
73
docs/framework/solid/guides/polling.md
Normal file
73
docs/framework/solid/guides/polling.md
Normal 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'
|
||||
272
docs/framework/solid/guides/prefetching.md
Normal file
272
docs/framework/solid/guides/prefetching.md
Normal 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'
|
||||
97
docs/framework/solid/guides/queries.md
Normal file
97
docs/framework/solid/guides/queries.md
Normal 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'
|
||||
165
docs/framework/solid/guides/query-cancellation.md
Normal file
165
docs/framework/solid/guides/query-cancellation.md
Normal 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'
|
||||
81
docs/framework/solid/guides/query-functions.md
Normal file
81
docs/framework/solid/guides/query-functions.md
Normal 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'
|
||||
15
docs/framework/solid/guides/query-invalidation.md
Normal file
15
docs/framework/solid/guides/query-invalidation.md
Normal 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',
|
||||
}
|
||||
---
|
||||
71
docs/framework/solid/guides/query-keys.md
Normal file
71
docs/framework/solid/guides/query-keys.md
Normal 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'
|
||||
56
docs/framework/solid/guides/query-options.md
Normal file
56
docs/framework/solid/guides/query-options.md
Normal 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'
|
||||
14
docs/framework/solid/guides/query-retries.md
Normal file
14
docs/framework/solid/guides/query-retries.md
Normal 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(() => ',
|
||||
}
|
||||
---
|
||||
209
docs/framework/solid/guides/request-waterfalls.md
Normal file
209
docs/framework/solid/guides/request-waterfalls.md
Normal 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'
|
||||
6
docs/framework/solid/guides/scroll-restoration.md
Normal file
6
docs/framework/solid/guides/scroll-restoration.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
id: scroll-restoration
|
||||
title: Scroll Restoration
|
||||
ref: docs/framework/react/guides/scroll-restoration.md
|
||||
replace: { 'React Router’s ScrollRestoration, ': '' }
|
||||
---
|
||||
6
docs/framework/solid/guides/ssr.md
Normal file
6
docs/framework/solid/guides/ssr.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
id: ssr
|
||||
title: SSR
|
||||
---
|
||||
|
||||
Will come soon
|
||||
41
docs/framework/solid/guides/suspense.md
Normal file
41
docs/framework/solid/guides/suspense.md
Normal 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.
|
||||
4
docs/framework/solid/guides/testing.md
Normal file
4
docs/framework/solid/guides/testing.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
id: testing
|
||||
title: Testing
|
||||
---
|
||||
@@ -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'
|
||||
20
docs/framework/solid/guides/window-focus-refetching.md
Normal file
20
docs/framework/solid/guides/window-focus-refetching.md
Normal 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'
|
||||
Reference in New Issue
Block a user