Skip to content

React ​

Framework

React · tanstack-fetch/react · @tanstack/react-query

Optional helpers from tanstack-fetch/react.

Live demo ​

Live demo · React / TanStack Query

useQuery + useMutation shape

Real createFetch calls — same queryFn / mutationFn contract your React app uses.

idle — click useQuery

const api = createFetch({ baseUrl })

// React Query
useQuery({
  queryKey: ['users'],
  queryFn: ({ signal }) => api.get<User[]>('/users', { signal }),
})

useMutation({
  mutationFn: (body) => api.post<User>('/users', { body }),
})

FetchProvider ​

Optional — only if you want useFetch() from context. For SSE you can skip it and pass client to useSse.

tsx
import { FetchProvider, useFetch } from 'tanstack-fetch/react'
import { useQuery } from '@tanstack/react-query'
import { isFetchError } from 'tanstack-fetch'

const App = () => (
  <FetchProvider
    baseUrl={import.meta.env.VITE_API_URL}
    getToken={() => localStorage.getItem('access_token')}
    onUnauthorized={() => {
      localStorage.removeItem('access_token')
      window.location.href = '/login'
    }}
    plugins={['trace', 'retry-idempotent']}
  >
    <UsersPage />
  </FetchProvider>
)

const UsersPage = () => {
  const api = useFetch()
  const { data, error, isPending } = useQuery({
    queryKey: ['users'],
    queryFn: ({ signal }) => api.get<User[]>('/users', { signal }),
  })

  if (isPending) return <p>Loading…</p>
  if (isFetchError(error))
    return (
      <p>
        {error.status}: {error.message}
      </p>
    )
  return (
    <ul>
      {data.map((u) => (
        <li key={u.id}>{u.name}</li>
      ))}
    </ul>
  )
}

useSse ​

Create the client from tanstack-fetch/sse, then pass it as { client: api }. FetchProvider is not required.

status: 'connecting' | 'connected' | 'disconnected' | 'error'

Live demo ​

Live demo · useSse

React useSse + status

Pass {'{ client: api }'} — no FetchProvider required. Status: connecting → connected → disconnected / error.

status: disconnected

import { createFetch } from 'tanstack-fetch/sse'
import { useSse } from 'tanstack-fetch/react'

const api = createFetch({
  baseUrl: import.meta.env.VITE_API_URL,
  plugins: ['sse-resume'],
  getToken: () => localStorage.getItem('access_token'),
})

const OrdersLive = () => {
  // Pass client — FetchProvider is optional
  const { data, status, error, close } = useSse<Tick>('/orders/stream', {
    client: api,
  })

  if (status === 'error') return <p>{error?.message}</p>

  return (
    <div>
      <p>{status}</p>
      <p>{data ? `#${data.n}` : '—'}</p>
      <button type="button" onClick={close}>Disconnect</button>
    </div>
  )
}
tsx
import { createFetch } from 'tanstack-fetch/sse'
import { useSse } from 'tanstack-fetch/react'

type OrderEvent = { id: string; status: string }

const api = createFetch({
  baseUrl: import.meta.env.VITE_API_URL,
  plugins: ['sse-resume'],
  getToken: () => localStorage.getItem('access_token'),
})

const OrdersLive = () => {
  const { data, status, error, close } = useSse<OrderEvent>('/orders/stream', {
    client: api,
    onMessage: (payload) => console.log('tick', payload),
  })

  if (status === 'error') return <p>Stream failed: {error?.message}</p>

  return (
    <div>
      <p>status: {status}</p>
      <p>{data ? `${data.id} · ${data.status}` : 'waiting…'}</p>
      <button type="button" onClick={close} disabled={status === 'disconnected'}>
        Disconnect
      </button>
    </div>
  )
}

useWs ​

Create the client from tanstack-fetch/live and pass { client: api }. Same result shape as useSse, plus send. In the browser, set authIn ('query' or 'protocol') when you have a token. Add transport: 'webtransport' for WebTransport. See WebSocket, WebTransport & streams.

tsx
import { createFetch } from 'tanstack-fetch/live'
import { useWs } from 'tanstack-fetch/react'

type ChatMessage = { user: string; text: string }

const api = createFetch({
  baseUrl: import.meta.env.VITE_API_URL,
  getToken: () => localStorage.getItem('access_token'),
})

const ChatRoom = ({ id }: { id: number }) => {
  const { data, status, error, send, close } = useWs<ChatMessage, { id: string }>('/rooms/:id', {
    client: api,
    params: { id },
    authIn: 'query',
  })

  if (status === 'error') return <p>Socket error: {String(error)}</p>

  return (
    <div>
      <p>status: {status}</p>
      <p>{data?.text ?? 'waiting…'}</p>
      <button type="button" onClick={() => send({ text: 'hi' })}>
        Send
      </button>
      <button type="button" onClick={close}>
        Disconnect
      </button>
    </div>
  )
}

Optional: via FetchProvider ​

If the tree already has <FetchProvider client={api}>, you can omit client and useSse reads it from context.

Cloneable app: examples/sse-live · SSE guide

MIT Licensed · Created by Mohammad Garmabi · Not an official TanStack package