File: playground.md | Updated: 11/15/2025
Search...
+ K
Auto
Docs Examples GitHub Contributors
Docs Examples GitHub Contributors
Docs Examples GitHub Contributors
Docs Examples Github Contributors
Docs Examples Github Contributors
Docs Examples Github Contributors
Docs Examples Github Contributors
Docs Examples Github Contributors
Maintainers Partners Support Learn StatsBETA Discord Merch Blog GitHub Ethos Brand Guide
Documentation
Framework
React
Version
v4
Search...
+ K
Menu
Getting Started
Guides & Concepts
Community Resources
API Reference
ESLint
Plugins
Examples
Framework
React
Version
v4
Menu
Getting Started
Guides & Concepts
Community Resources
API Reference
ESLint
Plugins
Examples
React Example: Playground
===========================================================================================================================================================================================================================================================================================================================================================================================================
Code ExplorerCode
Interactive SandboxSandbox
public
src
index.jsx
styles.css
.eslintrc
.gitignore
README.md
index.html
package.json
jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import {
QueryClient,
QueryClientProvider,
useQuery,
useQueryClient,
useMutation,
} from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import './styles.css'
let id = 0
let list = [\
'apple',\
'banana',\
'pineapple',\
'grapefruit',\
'dragonfruit',\
'grapes',\
].map((d) => ({ id: id++, name: d, notes: 'These are some notes' }))
let errorRate = 0.05
let queryTimeMin = 1000
let queryTimeMax = 2000
const queryClient = new QueryClient()
function Root() {
const [staleTime, setStaleTime] = React.useState(1000)
const [cacheTime, setCacheTime] = React.useState(3000)
const [localErrorRate, setErrorRate] = React.useState(errorRate)
const [localFetchTimeMin, setLocalFetchTimeMin] = React.useState(queryTimeMin)
const [localFetchTimeMax, setLocalFetchTimeMax] = React.useState(queryTimeMax)
React.useEffect(() => {
errorRate = localErrorRate
queryTimeMin = localFetchTimeMin
queryTimeMax = localFetchTimeMax
}, [localErrorRate, localFetchTimeMax, localFetchTimeMin])
React.useEffect(() => {
queryClient.setDefaultOptions({
queries: {
staleTime,
cacheTime,
},
})
}, [cacheTime, staleTime])
return (
<QueryClientProvider client={queryClient}>
<p>
The "staleTime" and "cacheTime" durations have been altered in this
example to show how query stale-ness and query caching work on a
granular level
</p>
<div>
Stale Time:{' '}
<input
type="number"
min="0"
step="1000"
value={staleTime}
onChange={(e) => setStaleTime(parseFloat(e.target.value, 10))}
style={{ width: '100px' }}
/>
</div>
<div>
Cache Time:{' '}
<input
type="number"
min="0"
step="1000"
value={cacheTime}
onChange={(e) => setCacheTime(parseFloat(e.target.value, 10))}
style={{ width: '100px' }}
/>
</div>
<br />
<div>
Error Rate:{' '}
<input
type="number"
min="0"
max="1"
step=".05"
value={localErrorRate}
onChange={(e) => setErrorRate(parseFloat(e.target.value, 10))}
style={{ width: '100px' }}
/>
</div>
<div>
Fetch Time Min:{' '}
<input
type="number"
min="1"
step="500"
value={localFetchTimeMin}
onChange={(e) => setLocalFetchTimeMin(parseFloat(e.target.value, 10))}
style={{ width: '60px' }}
/>{' '}
</div>
<div>
Fetch Time Max:{' '}
<input
type="number"
min="1"
step="500"
value={localFetchTimeMax}
onChange={(e) => setLocalFetchTimeMax(parseFloat(e.target.value, 10))}
style={{ width: '60px' }}
/>
</div>
<br />
<App />
<br />
<ReactQueryDevtools initialIsOpen />
</QueryClientProvider>
)
}
function App() {
const queryClient = useQueryClient()
const [editingIndex, setEditingIndex] = React.useState(null)
const [views, setViews] = React.useState(['', 'fruit', 'grape'])
// const [views, setViews] = React.useState([""]);
return (
<div className="App">
<div>
<button onClick={() => queryClient.invalidateQueries()}>
Force Refetch All
</button>
</div>
<br />
<hr />
{views.map((view, index) => (
<div key={index}>
<Todos
initialFilter={view}
setEditingIndex={setEditingIndex}
onRemove={() => {
setViews((old) => [...old, ''])
}}
/>
<br />
</div>
))}
<button
onClick={() => {
setViews((old) => [...old, ''])
}}
>
Add Filter List
</button>
<hr />
{editingIndex !== null ? (
<>
<EditTodo
editingIndex={editingIndex}
setEditingIndex={setEditingIndex}
/>
<hr />
</>
) : null}
<AddTodo />
</div>
)
}
function Todos({ initialFilter = '', setEditingIndex }) {
const [filter, setFilter] = React.useState(initialFilter)
const { status, data, isFetching, error, failureCount, refetch } = useQuery({
queryKey: ['todos', { filter }],
queryFn: fetchTodos,
})
return (
<div>
<div>
<label>
Filter:{' '}
<input value={filter} onChange={(e) => setFilter(e.target.value)} />
</label>
</div>
{status === 'loading' ? (
<span>Loading... (Attempt: {failureCount + 1})</span>
) : status === 'error' ? (
<span>
Error: {error.message}
<br />
<button onClick={() => refetch()}>Retry</button>
</span>
) : (
<>
<ul>
{data
? data.map((todo) => (
<li key={todo.id}>
{todo.name}{' '}
<button onClick={() => setEditingIndex(todo.id)}>
Edit
</button>
</li>
))
: null}
</ul>
<div>
{isFetching ? (
<span>
Background Refreshing... (Attempt: {failureCount + 1})
</span>
) : (
<span> </span>
)}
</div>
</>
)}
</div>
)
}
function EditTodo({ editingIndex, setEditingIndex }) {
const queryClient = useQueryClient()
// Don't attempt to query until editingIndex is truthy
const { status, data, isFetching, error, failureCount, refetch } = useQuery({
queryKey: ['todo', { id: editingIndex }],
queryFn: () => fetchTodoById({ id: editingIndex }),
enabled: editingIndex !== null,
})
const [todo, setTodo] = React.useState(data || {})
React.useEffect(() => {
if (editingIndex !== null && data) {
setTodo(data)
} else {
setTodo({})
}
}, [data, editingIndex])
const saveMutation = useMutation({
mutationFn: patchTodo,
onSuccess: (data) => {
// Update `todos` and the individual todo queries when this mutation succeeds
queryClient.invalidateQueries({ queryKey: ['todos'] })
queryClient.setQueryData(['todo', { id: editingIndex }], data)
},
})
const onSave = () => {
saveMutation.mutate(todo)
}
const disableEditSave =
status === 'loading' || saveMutation.status === 'loading'
return (
<div>
<div>
{data ? (
<>
<button onClick={() => setEditingIndex(null)}>Back</button> Editing
Todo "{data.name}" (#
{editingIndex})
</>
) : null}
</div>
{status === 'loading' ? (
<span>Loading... (Attempt: {failureCount + 1})</span>
) : error ? (
<span>
Error! <button onClick={() => refetch()}>Retry</button>
</span>
) : (
<>
<label>
Name:{' '}
<input
value={todo.name}
onChange={(e) =>
e.persist() ||
setTodo((old) => ({ ...old, name: e.target.value }))
}
disabled={disableEditSave}
/>
</label>
<label>
Notes:{' '}
<input
value={todo.notes}
onChange={(e) =>
e.persist() ||
setTodo((old) => ({ ...old, notes: e.target.value }))
}
disabled={disableEditSave}
/>
</label>
<div>
<button onClick={onSave} disabled={disableEditSave}>
Save
</button>
</div>
<div>
{saveMutation.status === 'loading'
? 'Saving...'
: saveMutation.status === 'error'
? saveMutation.error.message
: 'Saved!'}
</div>
<div>
{isFetching ? (
<span>
Background Refreshing... (Attempt: {failureCount + 1})
</span>
) : (
<span> </span>
)}
</div>
</>
)}
</div>
)
}
function AddTodo() {
const queryClient = useQueryClient()
const [name, setName] = React.useState('')
const addMutation = useMutation({
mutationFn: postTodo,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] })
},
})
return (
<div>
<input
value={name}
onChange={(e) => setName(e.target.value)}
disabled={addMutation.status === 'loading'}
/>
<button
onClick={() => {
addMutation.mutate({ name })
}}
disabled={addMutation.status === 'loading' || !name}
>
Add Todo
</button>
<div>
{addMutation.status === 'loading'
? 'Saving...'
: addMutation.status === 'error'
? addMutation.error.message
: 'Saved!'}
</div>
</div>
)
}
function fetchTodos({ signal, queryKey: [, { filter }] }) {
console.info('fetchTodos', { filter })
if (signal) {
signal.addEventListener('abort', () => {
console.info('cancelled', filter)
})
}
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < errorRate) {
return reject(
new Error(JSON.stringify({ fetchTodos: { filter } }, null, 2)),
)
}
resolve(list.filter((d) => d.name.includes(filter)))
}, queryTimeMin + Math.random() * (queryTimeMax - queryTimeMin))
})
}
function fetchTodoById({ id }) {
console.info('fetchTodoById', { id })
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < errorRate) {
return reject(
new Error(JSON.stringify({ fetchTodoById: { id } }, null, 2)),
)
}
resolve(list.find((d) => d.id === id))
}, queryTimeMin + Math.random() * (queryTimeMax - queryTimeMin))
})
}
function postTodo({ name, notes }) {
console.info('postTodo', { name, notes })
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < errorRate) {
return reject(
new Error(JSON.stringify({ postTodo: { name, notes } }, null, 2)),
)
}
const todo = { name, notes, id: id++ }
list = [...list, todo]
resolve(todo)
}, queryTimeMin + Math.random() * (queryTimeMax - queryTimeMin))
})
}
function patchTodo(todo) {
console.info('patchTodo', todo)
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < errorRate) {
return reject(new Error(JSON.stringify({ patchTodo: todo }, null, 2)))
}
list = list.map((d) => {
if (d.id === todo.id) {
return todo
}
return d
})
resolve(todo)
}, queryTimeMin + Math.random() * (queryTimeMax - queryTimeMin))
})
}
const rootElement = document.getElementById('root')
ReactDOM.createRoot(rootElement).render(<Root />)
import React from 'react'
import ReactDOM from 'react-dom/client'
import {
QueryClient,
QueryClientProvider,
useQuery,
useQueryClient,
useMutation,
} from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import './styles.css'
let id = 0
let list = [\
'apple',\
'banana',\
'pineapple',\
'grapefruit',\
'dragonfruit',\
'grapes',\
].map((d) => ({ id: id++, name: d, notes: 'These are some notes' }))
let errorRate = 0.05
let queryTimeMin = 1000
let queryTimeMax = 2000
const queryClient = new QueryClient()
function Root() {
const [staleTime, setStaleTime] = React.useState(1000)
const [cacheTime, setCacheTime] = React.useState(3000)
const [localErrorRate, setErrorRate] = React.useState(errorRate)
const [localFetchTimeMin, setLocalFetchTimeMin] = React.useState(queryTimeMin)
const [localFetchTimeMax, setLocalFetchTimeMax] = React.useState(queryTimeMax)
React.useEffect(() => {
errorRate = localErrorRate
queryTimeMin = localFetchTimeMin
queryTimeMax = localFetchTimeMax
}, [localErrorRate, localFetchTimeMax, localFetchTimeMin])
React.useEffect(() => {
queryClient.setDefaultOptions({
queries: {
staleTime,
cacheTime,
},
})
}, [cacheTime, staleTime])
return (
<QueryClientProvider client={queryClient}>
<p>
The "staleTime" and "cacheTime" durations have been altered in this
example to show how query stale-ness and query caching work on a
granular level
</p>
<div>
Stale Time:{' '}
<input
type="number"
min="0"
step="1000"
value={staleTime}
onChange={(e) => setStaleTime(parseFloat(e.target.value, 10))}
style={{ width: '100px' }}
/>
</div>
<div>
Cache Time:{' '}
<input
type="number"
min="0"
step="1000"
value={cacheTime}
onChange={(e) => setCacheTime(parseFloat(e.target.value, 10))}
style={{ width: '100px' }}
/>
</div>
<br />
<div>
Error Rate:{' '}
<input
type="number"
min="0"
max="1"
step=".05"
value={localErrorRate}
onChange={(e) => setErrorRate(parseFloat(e.target.value, 10))}
style={{ width: '100px' }}
/>
</div>
<div>
Fetch Time Min:{' '}
<input
type="number"
min="1"
step="500"
value={localFetchTimeMin}
onChange={(e) => setLocalFetchTimeMin(parseFloat(e.target.value, 10))}
style={{ width: '60px' }}
/>{' '}
</div>
<div>
Fetch Time Max:{' '}
<input
type="number"
min="1"
step="500"
value={localFetchTimeMax}
onChange={(e) => setLocalFetchTimeMax(parseFloat(e.target.value, 10))}
style={{ width: '60px' }}
/>
</div>
<br />
<App />
<br />
<ReactQueryDevtools initialIsOpen />
</QueryClientProvider>
)
}
function App() {
const queryClient = useQueryClient()
const [editingIndex, setEditingIndex] = React.useState(null)
const [views, setViews] = React.useState(['', 'fruit', 'grape'])
// const [views, setViews] = React.useState([""]);
return (
<div className="App">
<div>
<button onClick={() => queryClient.invalidateQueries()}>
Force Refetch All
</button>
</div>
<br />
<hr />
{views.map((view, index) => (
<div key={index}>
<Todos
initialFilter={view}
setEditingIndex={setEditingIndex}
onRemove={() => {
setViews((old) => [...old, ''])
}}
/>
<br />
</div>
))}
<button
onClick={() => {
setViews((old) => [...old, ''])
}}
>
Add Filter List
</button>
<hr />
{editingIndex !== null ? (
<>
<EditTodo
editingIndex={editingIndex}
setEditingIndex={setEditingIndex}
/>
<hr />
</>
) : null}
<AddTodo />
</div>
)
}
function Todos({ initialFilter = '', setEditingIndex }) {
const [filter, setFilter] = React.useState(initialFilter)
const { status, data, isFetching, error, failureCount, refetch } = useQuery({
queryKey: ['todos', { filter }],
queryFn: fetchTodos,
})
return (
<div>
<div>
<label>
Filter:{' '}
<input value={filter} onChange={(e) => setFilter(e.target.value)} />
</label>
</div>
{status === 'loading' ? (
<span>Loading... (Attempt: {failureCount + 1})</span>
) : status === 'error' ? (
<span>
Error: {error.message}
<br />
<button onClick={() => refetch()}>Retry</button>
</span>
) : (
<>
<ul>
{data
? data.map((todo) => (
<li key={todo.id}>
{todo.name}{' '}
<button onClick={() => setEditingIndex(todo.id)}>
Edit
</button>
</li>
))
: null}
</ul>
<div>
{isFetching ? (
<span>
Background Refreshing... (Attempt: {failureCount + 1})
</span>
) : (
<span> </span>
)}
</div>
</>
)}
</div>
)
}
function EditTodo({ editingIndex, setEditingIndex }) {
const queryClient = useQueryClient()
// Don't attempt to query until editingIndex is truthy
const { status, data, isFetching, error, failureCount, refetch } = useQuery({
queryKey: ['todo', { id: editingIndex }],
queryFn: () => fetchTodoById({ id: editingIndex }),
enabled: editingIndex !== null,
})
const [todo, setTodo] = React.useState(data || {})
React.useEffect(() => {
if (editingIndex !== null && data) {
setTodo(data)
} else {
setTodo({})
}
}, [data, editingIndex])
const saveMutation = useMutation({
mutationFn: patchTodo,
onSuccess: (data) => {
// Update `todos` and the individual todo queries when this mutation succeeds
queryClient.invalidateQueries({ queryKey: ['todos'] })
queryClient.setQueryData(['todo', { id: editingIndex }], data)
},
})
const onSave = () => {
saveMutation.mutate(todo)
}
const disableEditSave =
status === 'loading' || saveMutation.status === 'loading'
return (
<div>
<div>
{data ? (
<>
<button onClick={() => setEditingIndex(null)}>Back</button> Editing
Todo "{data.name}" (#
{editingIndex})
</>
) : null}
</div>
{status === 'loading' ? (
<span>Loading... (Attempt: {failureCount + 1})</span>
) : error ? (
<span>
Error! <button onClick={() => refetch()}>Retry</button>
</span>
) : (
<>
<label>
Name:{' '}
<input
value={todo.name}
onChange={(e) =>
e.persist() ||
setTodo((old) => ({ ...old, name: e.target.value }))
}
disabled={disableEditSave}
/>
</label>
<label>
Notes:{' '}
<input
value={todo.notes}
onChange={(e) =>
e.persist() ||
setTodo((old) => ({ ...old, notes: e.target.value }))
}
disabled={disableEditSave}
/>
</label>
<div>
<button onClick={onSave} disabled={disableEditSave}>
Save
</button>
</div>
<div>
{saveMutation.status === 'loading'
? 'Saving...'
: saveMutation.status === 'error'
? saveMutation.error.message
: 'Saved!'}
</div>
<div>
{isFetching ? (
<span>
Background Refreshing... (Attempt: {failureCount + 1})
</span>
) : (
<span> </span>
)}
</div>
</>
)}
</div>
)
}
function AddTodo() {
const queryClient = useQueryClient()
const [name, setName] = React.useState('')
const addMutation = useMutation({
mutationFn: postTodo,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] })
},
})
return (
<div>
<input
value={name}
onChange={(e) => setName(e.target.value)}
disabled={addMutation.status === 'loading'}
/>
<button
onClick={() => {
addMutation.mutate({ name })
}}
disabled={addMutation.status === 'loading' || !name}
>
Add Todo
</button>
<div>
{addMutation.status === 'loading'
? 'Saving...'
: addMutation.status === 'error'
? addMutation.error.message
: 'Saved!'}
</div>
</div>
)
}
function fetchTodos({ signal, queryKey: [, { filter }] }) {
console.info('fetchTodos', { filter })
if (signal) {
signal.addEventListener('abort', () => {
console.info('cancelled', filter)
})
}
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < errorRate) {
return reject(
new Error(JSON.stringify({ fetchTodos: { filter } }, null, 2)),
)
}
resolve(list.filter((d) => d.name.includes(filter)))
}, queryTimeMin + Math.random() * (queryTimeMax - queryTimeMin))
})
}
function fetchTodoById({ id }) {
console.info('fetchTodoById', { id })
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < errorRate) {
return reject(
new Error(JSON.stringify({ fetchTodoById: { id } }, null, 2)),
)
}
resolve(list.find((d) => d.id === id))
}, queryTimeMin + Math.random() * (queryTimeMax - queryTimeMin))
})
}
function postTodo({ name, notes }) {
console.info('postTodo', { name, notes })
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < errorRate) {
return reject(
new Error(JSON.stringify({ postTodo: { name, notes } }, null, 2)),
)
}
const todo = { name, notes, id: id++ }
list = [...list, todo]
resolve(todo)
}, queryTimeMin + Math.random() * (queryTimeMax - queryTimeMin))
})
}
function patchTodo(todo) {
console.info('patchTodo', todo)
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < errorRate) {
return reject(new Error(JSON.stringify({ patchTodo: todo }, null, 2)))
}
list = list.map((d) => {
if (d.id === todo.id) {
return todo
}
return d
})
resolve(todo)
}, queryTimeMin + Math.random() * (queryTimeMax - queryTimeMin))
})
}
const rootElement = document.getElementById('root')
ReactDOM.createRoot(rootElement).render(<Root />)
[###### Want to Skip the Docs?
Query.gg - The Official React Query Course
\
“If you’re serious about *really* understanding React Query, there’s no better way than with query.gg”—Tanner Linsley
Learn More](https://query.gg/?s=tanstack)
You are currently reading v4 docs. Redirect to latest version?
Latest Hide
