TanStack

React Example: Batching

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 />)