Declarative data fetching and caching for re-frame, inspired by TanStack Query and RTK Query.
:on-success / :on-failure plumbingcache-time-ms via per-query timers (same model as TanStack Query):skip? true until a condition is met (e.g., dependent queries):on-start, :on-success, :on-failure for optimistic updates and rollback;; deps.edn
{:deps {com.shipclojure/re-frame-query {:mvn/version "0.2.0"}}}
;; Leiningen/Boot
[com.shipclojure/re-frame-query "0.2.0"]
(ns my-app.queries
(:require [re-frame.query :as rfq]))
(rfq/init!
{:default-effect-fn
(fn [request on-success on-failure]
{:http (assoc request :on-success on-success :on-failure on-failure)})
:queries
{:todos/list
{:query-fn (fn [{:keys [user-id]}]
{:method :get
:url (str "/api/users/" user-id "/todos")})
:stale-time-ms 30000
:cache-time-ms (* 5 60 1000)
:tags (fn [{:keys [user-id]}]
[[:todos :user user-id]])}}
:mutations
{:todos/add
{:mutation-fn (fn [{:keys [user-id title]}]
{:method :post
:url (str "/api/users/" user-id "/todos")
:body {:title title}})
:invalidates (fn [{:keys [user-id]}]
[[:todos :user user-id]])}}})
No :on-success / :on-failure wiring needed — the library auto-injects callbacks via your default-effect-fn.
Incremental API — You can also register queries and mutations one at a time with
rfq/reg-query,rfq/reg-mutation, andrfq/set-default-effect-fn!.
Think of (rf/subscribe [::rfq/query k params]) like a use-query hook —
subscribing is all you need. It triggers the fetch, caches the result, and
keeps it fresh.
(defn todos-view []
(let [{:keys [status data error fetching?]}
@(rf/subscribe [::rfq/query :todos/list {:user-id 42}])]
(case status
:loading [:div "Loading..."]
:error [:div "Error: " (pr-str error)]
:success [:div
[:ul (for [todo data]
^{:key (:id todo)}
[:li (:title todo)])]
(when fetching? [:span "Refreshing..."])]
[:div "Idle"])))
Unlike a typical re-frame subscription that just reads from app-db, ::rfq/query is built with reg-sub-raw — it uses Reagent's Reaction lifecycle to manage the query automatically:
A note on re-frame philosophy: re-frame recommends that subscriptions be pure reads. Our
::rfq/querydispatches events as a side effect of subscribing — a deliberate trade-off mirroring React Query'suseQuery. If you prefer explicit control, dispatch::rfq/ensure-queryand::rfq/mark-activeyourself and use the passive derived subscriptions (::rfq/query-data, etc.) instead.
(rf/dispatch [::rfq/execute-mutation :todos/add {:user-id 42 :title "Ship it"}])
On success, mutations automatically invalidate matching tags — all active queries with those tags are refetched.
(rf/dispatch [::rfq/invalidate-tags [[:todos :user 42]]])
| Guide | Description |
|---|---|
| API Reference | Events, subscriptions, config keys, query state shape |
| Status Tracking | How :status and :fetching? distinguish loading states |
| Garbage Collection | Per-query timer-based cache eviction |
| Polling | Query-level, per-subscription, and multi-subscriber polling |
| Conditional Fetching | :skip? for dependent queries |
| Prefetching | Pre-populate cache on hover or route transition |
| Where Data Lives | app-db layout, inspectability, serialization |
| Effect Overrides | Per-query transport, custom callbacks |
| Mutation Hooks | Lifecycle hooks, optimistic updates, request cancellation |
| Infinite Queries | Cursor-based pagination, sequential re-fetch, sliding window |
[::rfq/query k params] fetches data (if absent/stale) and marks the query activequery-fn returns a request map; the library wraps it with callbacks via effect-fnsetTimeout based on cache-time-msTwo full example apps with 8 tabs each (Basic CRUD, Polling, Dependent Queries, Prefetching, Mutation Lifecycle, WebSocket, Optimistic Updates, Infinite Scroll):
| App | Framework | Port | Directory |
|---|---|---|---|
| Reagent | Reagent + re-frame | 8710 | examples/reagent-app/ |
| UIx | UIx v2 + re-frame | 8720 | examples/uix-app/ |
Both use MSW (Mock Service Worker) to intercept fetch requests with an in-memory API.
cd examples/reagent-app # or examples/uix-app
pnpm install && pnpm run mocks && pnpm exec shadow-cljs watch demo
# Run unit tests
bb test:unit
# Run e2e tests (both example apps)
bb test:e2e
# Format code
bb fmt
# Check formatting
bb fmt:check
MIT
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 |