Skip to Content
ExamplesActivity and preload

Activity and preload

Compare three strategies for the same ~1s tab fetch:

  1. No prefetch — mount on click; Suspense fallback is visible
  2. Hover preloadpreloadObservablePromise on mouseenter warms the cache
  3. Activity pre-render — hidden <Activity> starts use(promise) fetches up front
import {Activity, Suspense, useMemo, useState} from 'react'
import {preloadObservablePromise} from 'react-rx'

import {fetchTab$} from './api'
import TabPanel from './TabPanel'

type Strategy = 'none' | 'preload' | 'activity'

const TABS = ['Posts', 'Photos', 'Settings'] as const

function TabButton({
  tab,
  active,
  strategy,
  onSelect,
}: {
  tab: (typeof TABS)[number]
  active: boolean
  strategy: Strategy
  onSelect: () => void
}) {
  const data$ = useMemo(() => fetchTab$(tab), [tab])

  return (
    <button
      type="button"
      onMouseEnter={() => {
        if (strategy === 'preload') {
          void preloadObservablePromise(data$, {ttl: 10_000})
        }
      }}
      onClick={onSelect}
      style={{fontWeight: active ? 700 : 400}}
    >
      {tab}
    </button>
  )
}

export default function App() {
  const [strategy, setStrategy] = useState<Strategy>('none')
  const [active, setActive] = useState<(typeof TABS)[number]>('Posts')

  return (
    <div style={{fontFamily: 'system-ui', padding: 16, maxWidth: 480}}>
      <h2 style={{marginTop: 0}}>Prefetch strategies</h2>
      <p style={{fontSize: 14}}>
        Each tab fetch takes ~1s. Compare click-only loading vs hover preload vs
        hidden <code>Activity</code> pre-render.
      </p>

      <label style={{display: 'block', marginBottom: 12}}>
        Strategy:{' '}
        <select
          value={strategy}
          onChange={(e) => setStrategy(e.target.value as Strategy)}
        >
          <option value="none">No prefetch (fetch on reveal)</option>
          <option value="preload">Hover preload</option>
          <option value="activity">Activity pre-render</option>
        </select>
      </label>

      <div style={{display: 'flex', gap: 8, marginBottom: 16}}>
        {TABS.map((tab) => (
          <TabButton
            key={tab}
            tab={tab}
            active={active === tab}
            strategy={strategy}
            onSelect={() => setActive(tab)}
          />
        ))}
      </div>

      <Suspense fallback={<p style={{opacity: 0.7}}>🌀 Loading…</p>}>
        {strategy === 'activity' ? (
          TABS.map((tab) => (
            <Activity key={tab} mode={active === tab ? 'visible' : 'hidden'}>
              <TabPanel tab={tab} />
            </Activity>
          ))
        ) : (
          <TabPanel tab={active} />
        )}
      </Suspense>
    </div>
  )
}

Open on CodeSandboxOpen Sandbox
Last updated on