Skip to Content
ExamplesSuspense data fetching

Suspense data fetching

useObservablePromise returns a promise for React’s use(). The Suspense fallback shows until the first emission; later stream updates do not re-trigger it.

import {Suspense, use, useMemo, useState} from 'react'
import {useObservablePromise} from 'react-rx'
import {map, timer} from 'rxjs'

/**
 * Simulated API with ~800ms latency. Later ticks update the same stream
 * without re-triggering the Suspense fallback.
 */
function createFeed$(id: string) {
  return timer(800, 2000).pipe(
    map((n) => ({
      id,
      message: `Update #${n + 1} for ${id}`,
      at: new Date().toLocaleTimeString(),
    })),
  )
}

function Feed({id}: {id: string}) {
  const feed$ = useMemo(() => createFeed$(id), [id])
  const promise = useObservablePromise(feed$)
  const data = use(promise)

  return (
    <div>
      <h3>{data.id}</h3>
      <p>{data.message}</p>
      <small>Last update: {data.at}</small>
    </div>
  )
}

export default function SuspenseExample() {
  const [id, setId] = useState('alpha')

  return (
    <div style={{fontFamily: 'system-ui', padding: 16}}>
      <p>
        <button type="button" onClick={() => setId('alpha')}>
          alpha
        </button>{' '}
        <button type="button" onClick={() => setId('beta')}>
          beta
        </button>
      </p>
      <Suspense
        key={id}
        fallback={<p style={{opacity: 0.7}}>Loading {id}</p>}
      >
        <Feed id={id} />
      </Suspense>
      <p style={{marginTop: 24, fontSize: 14, opacity: 0.75}}>
        Switching feeds re-suspends (new observable identity). After the first
        value, live updates arrive without showing the fallback again.
      </p>
    </div>
  )
}

Open on CodeSandboxOpen Sandbox
Last updated on