Queries can automatically refetch on an interval. Polling is configured via :polling-interval-ms, either at the query level (default for all subscribers) or at the subscription/event level (per-caller override). When multiple subscribers have different intervals, the lowest non-zero interval wins.
Set a default polling interval when registering the query:
(rfq/reg-query :stocks/prices
{:query-fn (fn [_] {:method :get :url "/api/stocks"})
:polling-interval-ms 5000}) ;; every 5 seconds
The ::rfq/query subscription handles the full lifecycle — fetching, active tracking, polling start/stop — automatically. It reads :polling-interval-ms from the query config:
;; Starts polling at 5s — no extra config needed
@(rf/subscribe [::rfq/query :stocks/prices {}])
For explicit lifecycle control (e.g. route-based navigation), use events for lifecycle and ::rfq/query-state for reading. mark-active reads :polling-interval-ms from its opts map or falls back to the query config:
;; On route enter — start fetching, mark active, and start polling
(rf/dispatch [::rfq/ensure-query :stocks/prices {}])
(rf/dispatch [::rfq/mark-active :stocks/prices {}]) ;; reads interval from query config
;; Or override the interval per-caller:
(rf/dispatch [::rfq/mark-active :stocks/prices {} {:polling-interval-ms 1000}])
;; In views — read with a passive sub (no side effects)
(let [{:keys [status data fetching?]}
@(rf/subscribe [::rfq/query-state :stocks/prices {}])]
...)
;; On route leave — stops polling and schedules GC
(rf/dispatch [::rfq/mark-inactive :stocks/prices {}])
:sub-id)Each polling subscriber is tracked by a :sub-id. Effectful subscriptions automatically use a unique ID per component instance. For event-based lifecycle, a :default sub-id is used when none is provided — this works well for the common case of one caller per query.
If multiple callers manage the same query independently (e.g. a dashboard and a sidebar both polling the same data), pass explicit :sub-id values so each caller only removes its own subscriber:
;; Dashboard route
(rf/dispatch [::rfq/mark-active :stocks/prices {} {:sub-id :dashboard}])
;; ...later
(rf/dispatch [::rfq/mark-inactive :stocks/prices {} {:sub-id :dashboard}])
;; Sidebar (different interval, independent lifecycle)
(rf/dispatch [::rfq/mark-active :stocks/prices {} {:sub-id :sidebar
:polling-interval-ms 1000}])
;; ...later — only removes the sidebar subscriber
(rf/dispatch [::rfq/mark-inactive :stocks/prices {} {:sub-id :sidebar}])
When multiple subscribers exist for the same query, the lowest non-zero interval wins — same as with effectful subscriptions.
Override or set the interval for a specific subscriber via the opts map:
;; This component polls at 1s, regardless of the query-level default
@(rf/subscribe [::rfq/query :stocks/prices {} {:polling-interval-ms 1000}])
;; Component A — polls at 5s (query-level default)
@(rf/subscribe [::rfq/query :stocks/prices {}])
;; Component B — polls at 1s (per-subscription override)
@(rf/subscribe [::rfq/query :stocks/prices {} {:polling-interval-ms 1000}])
;; Effective interval: 1s (the lowest non-zero)
;; When Component B unmounts → interval reverts to 5s
By default, if a query is already fetching when a poll tick fires, the tick is skipped. This prevents stale-response races — if request T0 stalls past the next interval, T1 won't fire a duplicate request that could later overwrite fresher data.
;; Default behavior — no config needed, ticks are skipped while fetching
(rfq/reg-query :metrics/live
{:query-fn (fn [_] {:method :get :url "/api/metrics"})
:polling-interval-ms 2000})
To opt out and fire every tick regardless of in-flight status (matches TanStack Query behavior), set :polling-mode :force:
(rfq/reg-query :metrics/live
{:query-fn (fn [_] {:method :get :url "/api/metrics"})
:polling-interval-ms 2000
:polling-mode :force}) ;; fire even if prior request is in-flight
Note: Manual
refetch-querycalls are always unconditional —:polling-modeonly affects automatic poll ticks.
Polling stops automatically when:
::rfq/mark-inactive is dispatchedNo manual cleanup needed in either case.
Polling is currently only supported for standard queries (::rfq/query). Infinite queries do not support automatic polling. If you have a use case for polling infinite queries, please open an issue.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |