snapshot of https://github.com/TanStack/query.git (main, 2026-09-16; blobs > 131072B omitted)
This commit is contained in:
60
examples/react/batching/src/character-loader.ts
Normal file
60
examples/react/batching/src/character-loader.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { AsyncBatcher } from '@tanstack/react-pacer/async-batcher'
|
||||
|
||||
interface CharacterT {
|
||||
id: number
|
||||
name: string
|
||||
status: string
|
||||
species: string
|
||||
type: string
|
||||
gender: string
|
||||
image: string
|
||||
}
|
||||
|
||||
type CharacterRequest = {
|
||||
deferred: PromiseWithResolvers<CharacterT | null>
|
||||
id: number
|
||||
}
|
||||
|
||||
// fetch function that returns characters by id
|
||||
const fetchCharacters = async (ids: ReadonlyArray<number>) => {
|
||||
console.log(`Fetching characters: ${ids.join(', ')}`)
|
||||
|
||||
const res = await fetch(
|
||||
`https://rickandmortyapi.com/api/character/[${ids.join(',')}]`,
|
||||
)
|
||||
const characters = (await res.json()) as Array<CharacterT> | CharacterT
|
||||
|
||||
const characterMap = new Map<number, CharacterT>()
|
||||
for (const character of Array.isArray(characters)
|
||||
? characters
|
||||
: [characters]) {
|
||||
characterMap.set(character.id, character)
|
||||
}
|
||||
|
||||
return characterMap
|
||||
}
|
||||
|
||||
const characterBatcher = new AsyncBatcher<CharacterRequest>(
|
||||
(requests) => fetchCharacters([...new Set(requests.map(({ id }) => id))]),
|
||||
{
|
||||
wait: 0,
|
||||
onSuccess: (charactersById, requests) => {
|
||||
for (const { deferred, id } of requests) {
|
||||
deferred.resolve(charactersById.get(id) ?? null)
|
||||
}
|
||||
},
|
||||
onError: (error, requests) => {
|
||||
for (const { deferred } of requests) {
|
||||
deferred.reject(error)
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
export const loadCharacter = (id: number) => {
|
||||
const deferred = Promise.withResolvers<CharacterT | null>()
|
||||
|
||||
characterBatcher.addItem({ deferred, id })
|
||||
|
||||
return deferred.promise
|
||||
}
|
||||
132
examples/react/batching/src/index.tsx
Normal file
132
examples/react/batching/src/index.tsx
Normal file
@@ -0,0 +1,132 @@
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import {
|
||||
QueryClient,
|
||||
QueryClientProvider,
|
||||
queryOptions,
|
||||
useQueries,
|
||||
} from '@tanstack/react-query'
|
||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
|
||||
import { useState } from 'react'
|
||||
import { loadCharacter } from './character-loader'
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: 0,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const characterQueryOptions = (id: number) =>
|
||||
queryOptions({
|
||||
queryKey: ['character', id],
|
||||
queryFn: () => loadCharacter(id),
|
||||
staleTime: 60 * 1000,
|
||||
})
|
||||
|
||||
function Characters({ ids }: { ids: Array<number> }) {
|
||||
const characters = useQueries({
|
||||
queries: ids.map((id) => characterQueryOptions(id)),
|
||||
})
|
||||
|
||||
return characters.map((character, index) => {
|
||||
if (character.status === 'pending') {
|
||||
return 'Loading...'
|
||||
}
|
||||
if (character.status === 'error') {
|
||||
return 'Error...'
|
||||
}
|
||||
if (character.data === null) {
|
||||
return 'Not found ...'
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={String(index)} style={{ display: 'flex' }}>
|
||||
<img
|
||||
width={100}
|
||||
height={100}
|
||||
alt={character.data.image}
|
||||
src={character.data.image}
|
||||
/>
|
||||
<div style={{ paddingLeft: 10, textAlign: 'left' }}>
|
||||
<h5 style={{ marginTop: 0, marginBottom: 5 }}>
|
||||
{character.data.name} (#{character.data.id})
|
||||
</h5>
|
||||
<p style={{ marginTop: 0, marginBottom: 2 }}>
|
||||
Status: {character.status}
|
||||
</p>
|
||||
<p style={{ marginTop: 0, marginBottom: 2 }}>
|
||||
Species: {character.data.species}
|
||||
</p>
|
||||
<p style={{ marginTop: 0, marginBottom: 2 }}>
|
||||
Gender: {character.data.gender}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
function App() {
|
||||
const [ids, setIds] = useState<Array<number>>([])
|
||||
const [idsToLoad, setIdsToLoad] = useState<Array<number>>([])
|
||||
const [error, setError] = useState('')
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<h2>Character fetcher</h2>
|
||||
<div>
|
||||
<label style={{ display: 'block' }}>
|
||||
Character ids (numberic, comma seprated)
|
||||
</label>
|
||||
<input
|
||||
style={{ display: 'inline-block' }}
|
||||
placeholder="ids"
|
||||
onChange={(event) => {
|
||||
const text = event.target.value.replace(' ', '')
|
||||
const ids = text.split(',').map((id) => parseInt(id, 10))
|
||||
const allValid = ids.reduce(
|
||||
(valid, id) => valid && !isNaN(id),
|
||||
true,
|
||||
)
|
||||
|
||||
if (allValid) {
|
||||
setIds(ids)
|
||||
setError('')
|
||||
} else {
|
||||
setError('Invalid.. only use numbers and comma')
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error ? <h3 style={{ color: 'red' }}>{error}</h3> : null}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setIdsToLoad(ids)
|
||||
}}
|
||||
>
|
||||
Load characters
|
||||
</button>
|
||||
<div>
|
||||
<h4 style={{ textAlign: 'left' }}>Characters:</h4>
|
||||
<Characters ids={idsToLoad} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Root() {
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<App />
|
||||
<ReactQueryDevtools initialIsOpen />
|
||||
</QueryClientProvider>
|
||||
)
|
||||
}
|
||||
|
||||
const rootElement = document.getElementById('root')
|
||||
if (!rootElement) throw new Error('Missing #root element')
|
||||
ReactDOM.createRoot(rootElement).render(<Root />)
|
||||
Reference in New Issue
Block a user