skip to content

An Inertia page refreshes a job-status table with usePoll every two seconds; how do its options, router.reload and router.cancelAll keep requests under control?

level: middleimportance: nice to knowfreq 18%

answer

  1. usePoll(ms, reloadOptions, pollOptions)
  2. each tick is a router.reload
  3. mode: overlap, cancel or rest
  4. 90% throttle in background tabs
  5. cancelAll replaced cancel in v3

basics

~10 s

usePoll(2000, { only: ['jobs'] }) runs router.reload on each tick and stops on unmount. mode decides overlap, cancel or rest, background tabs are throttled unless keepAlive, and router.cancelAll() aborts in-flight requests.

solid answer

~50 s

`usePoll(2000, { only: ['jobs'] })` from the React or Vue adapter calls `router.reload()` every two seconds and stops when the component unmounts; `reload` visits the current URL asynchronously with `preserveState` and `preserveScroll`, so the table updates without jumping. Pass `only` so each tick fetches just the polled prop. The third argument sets poll behaviour: `mode: 'overlap'` (default) fires even if the last request is still running, `'cancel'` aborts it, and `'rest'` waits the interval after each response, which stops a slow endpoint from piling up requests. Background tabs are throttled by 90% unless `keepAlive: true`, and `autoStart: false` with the returned `start`, `stop` and `polling` gives manual control. To abort everything in flight, for example before a destructive action, call `router.cancelAll()`, which in Inertia 3 replaced `router.cancel()` and cancels sync, async and prefetch requests unless told otherwise.

code

jsx · 23 lines
jsx
import { router, usePoll } from '@inertiajs/react'

export default function Jobs({ jobs }) {
  const { start, stop, polling } = usePoll(
    2000,
    { only: ['jobs'] },
    { mode: 'rest', keepAlive: false },
  )

  function purgeFailed() {
    stop()
    router.cancelAll({ prefetch: false })
    router.delete('/jobs/failed', { onFinish: start })
  }

  return (
    <section>
      <button onClick={polling ? stop : start}>{polling ? 'Pause' : 'Resume'}</button>
      <button onClick={purgeFailed}>Purge failed</button>
      <JobTable rows={jobs} />
    </section>
  )
}

go deeper

for a junior

Recall that usePoll reloads the page's props on an interval and stops when the component unmounts.

for a middle

Explain reload's preserve defaults, only for polled props, the three concurrency modes, background throttling and cancelAll's options.

for a senior

Size intervals and modes against endpoint latency, rate limits and session writes, and pause polling around mutations.

for a principal

Decide when polling should give way to broadcasting, weighing per-tab load against infrastructure complexity.

## The building block: `router.reload` `router.reload(options)` visits the **current URL** again. Compared with a plain `router.visit`, it: - runs as an **async** visit, so it does not cancel or block the user's own navigation; - sets `preserveState` and `preserveScroll` to `true`, so the table keeps its scroll position and local state; - accepts every visit option, most usefully `only` or `except` to fetch a subset of props. ## `usePoll` `usePoll(interval, requestOptions, pollOptions)` wraps `router.poll` in a hook: 1. `interval` is in milliseconds. 2. `requestOptions` are `router.reload` options, or a function returning them that is evaluated on every tick, so the request can include current component state. 3. `pollOptions` control scheduling: `keepAlive`, `autoStart` and `mode`. The hook starts on mount (unless `autoStart: false`), destroys the poll on unmount and returns `start`, `stop` and a `polling` flag, which says whether the poll is running, not whether a request is in flight. ## Concurrency modes | `mode` | Behaviour on each tick | Good for | |---|---|---| | `overlap` (default) | fires a new request even if the last is in flight | fast, cheap endpoints | | `cancel` | aborts the in-flight request, then fires | when only the newest answer matters | | `rest` | waits the interval after the previous request finishes | slow endpoints that must not stack up | With a job-status endpoint that sometimes takes three seconds, `overlap` at two seconds builds a queue of concurrent requests; `rest` turns the interval into a pause between responses. ## Background tabs When the tab is hidden, the poll is throttled by 90%, meaning only every tenth tick fires. Set `keepAlive: true` when the page must keep polling in the background, such as a monitor on a second screen. ## Cancelling: `router.cancelAll` `router.cancelAll()` aborts in-flight visits. By default it cancels **sync, async and prefetch** requests; pass `{ async: false, prefetch: false }` to cancel only the sync visit, or `{ prefetch: false }` to keep prefetches. It replaced v2's `router.cancel()`, which only cancelled the sync visit. For a single request, keep the token from `onCancelToken` and call `cancel()` on it; `onCancel` and `onFinish` fire. ## Server-side considerations Each tick is a full request through the Laravel stack: - **Only compute what is polled.** Wrap other props in closures so `only: ['jobs']` skips their queries. - **Session writes.** Frequent polls keep the session alive and write it on every request with the default database session driver; very short intervals add up. - **Rate limits.** If the route sits behind a throttle middleware, a two-second poll per open tab can hit the limit. - **Consider push.** When many users watch the same data, broadcasting over WebSockets may cost less than polling. ## Choosing an interval - Start from how fresh the data must be, not from how fast the server is. - Keep the interval longer than the endpoint's typical response time, or use `rest`. - Poll only the props that change, and make them closures on the server. - Stop polling while a modal edits the same data, so a tick does not overwrite what the user is looking at. ## Observing it In the network tab each tick is an Inertia request with `X-Inertia-Partial-Data: jobs` when `only` is set, and its JSON reply carries only that prop plus always props such as `errors`. With `mode: 'rest'` the gaps between requests stay constant even when a response is slow; with `overlap` you can see several requests in flight at once. Switching tabs away should drop the request rate to roughly one tenth. ## Version notes The `mode` option and function-valued request options arrived in client 3.2, and the returned `polling` flag in 3.7. `router.cancelAll()` with the `sync`, `async` and `prefetch` options is the v3 replacement for `router.cancel()`.

  • Why does a polled table not jump to the top on every tick?
    Each tick is a `router.reload()`, and reload visits default to `preserveScroll: true` and `preserveState: true`. The page component keeps its instance and local state, only the returned props change, and Inertia skips its usual scroll reset. With `only: ['jobs']` the rest of the page's props are not even refetched.
  • What did router.cancel() do in Inertia 2, and how do you reproduce it in 3?
    In v2, `router.cancel()` cancelled only the synchronous visit. In v3 it is gone and `router.cancelAll()` cancels synchronous, asynchronous and prefetch requests by default. `router.cancelAll({ async: false, prefetch: false })` reproduces the old behaviour.

saying these in an interview costs you the question

  • usePoll keeps running after the component unmounts.
  • Polling resets scroll position on every tick.
  • router.cancel() is still the way to abort visits in Inertia 3.
  • The default mode waits for the previous request before the next tick.
  • The polling flag shows whether a request is in flight.