Skip to content

re-frame.adapter.uix

The UIx adapter connects re-frame2's substrate-agnostic core to UIx, a hooks-first React substrate. It exposes:

  • the hooks use-subscribe, use-frame, use-resource-lease, and use-current-frame;
  • the frame-provider (SCOPE) + frame-root (ENSURE) components;
  • the adapter spec map you pass to init!;
  • adapter seams for tests, SSR, and code-gen.

It ships in the day8/re-frame2-uix artefact. The dependency direction is one-way: the adapter depends on re-frame.core, never the reverse. There is no auto-injection; UIx components read subscriptions with use-subscribe and take their frame ops (dispatch) off the use-frame hook directly.

(:require [re-frame.adapter.uix :as uix-adapter])

Register views by Var (the React-component idiom) or with rf/reg-view* for registry-keyed view addressing. reg-view (the Reagent macro) does not cover UIx. A minimal app wires the adapter into init! and reads subscriptions through the hook:

(:require [re-frame.core :as rf]
          [re-frame.adapter.uix :as uix-adapter]
          [uix.core :refer [$ defui]])

(rf/init! uix-adapter/adapter)

(defui cart-row [{:keys [item]}]
  (let [count (uix-adapter/use-subscribe [:cart/count])]
    ($ :tr
       ($ :td (:name item))
       ($ :td count))))

For narrative coverage and the substrate decision set, see Use UIx, Helix, or reagent-slim.

Adapter spec

adapter

  • Kind: Var (map)
  • Signature:
    {:kind                      :rf.adapter/uix
     :make-state-container      
     :read-container            
     :replace-container!        
     :subscribe-container       
     :make-derived-value        
     :render                    
     :render-to-string          
     :register-context-provider 
     :flush-render!             
     :dispose-adapter!          }
    
  • Description: The adapter spec passed to (rf/init! ...). Implements the same substrate adapter contract as the Reagent and Helix adapters. :kind is :rf.adapter/uix.
  • Example:
    (rf/init! uix-adapter/adapter)
    

Hooks

use-subscribe

  • Kind: UIx hook (function)
  • Signature:
    (use-subscribe query-v)  current sub value
    (use-subscribe frame-kw query-v)  current sub value
    
  • Description: Subscribe inside a UIx component. This is the hook-shaped equivalent of subscribe.

Returns the current sub value and re-renders the calling component when the value changes.

  • The 1-arg form resolves the frame through the standard chain: with-frame dynamic scope first, then the surrounding frame-provider. It raises :rf.error/no-frame-context when neither is in scope (there is no :rf/default floor).
  • The 2-arg form pins to an explicit frame-id, bypassing the chain.
  • Example:
    (defui cart-total []
      (let [total (uix-adapter/use-subscribe [:cart/total])]
        ($ :span total)))
    

use-frame

  • Kind: UIx hook (function)
  • Signature:
    (use-frame)  {:frame  :dispatch  :dispatch-sync  :subscribe }
    
  • Description: Returns the frame ops map for the ambient frame — exactly what (rf/capture-frame) returns (the frame-locked ops map), captured in hook position. This is how a UIx component gets hold of dispatch (and the other frame-locked ops) without auto-injection: destructure dispatch off it and close over that.

capture-frame is the hold primitive; reg-view injection (Reagent) and use-frame (UIx / Helix) are its two ergonomic spellings — one primitive, three faces.

  • Frame resolution matches use-subscribe: with-frame dynamic scope first, then the surrounding frame-provider / frame-root via React context. It raises :rf.error/no-frame-context when neither is in scope.
  • The returned map is reference-stable across re-renders for the same resolved frame (safe in effect deps and child props); a provider swap re-renders the caller and yields a map locked to the new frame.
  • No options map, no variants — for an explicit frame, call (rf/capture-frame frame-id) directly.
  • Example:
    (defui counter-buttons []
      (let [count              (uix-adapter/use-subscribe [:counter/value])
            {:keys [dispatch]} (uix-adapter/use-frame)]
        ($ :button {:on-click #(dispatch [:counter/inc])} "+")))
    

use-resource-lease

  • Kind: UIx hook (function)
  • Signature:
    (use-resource-lease descriptor)  nil
    (use-resource-lease descriptor opts)  nil
    
  • Description: Holds a resource liveness lease for the calling component's mounted lifetime. On mount, dispatches :rf.resource/ensure with an app-minted [:lease …] owner; on unmount, dispatches :rf.resource/release-owner for that same lease.

Returns nil: this is a lifecycle hook, not a read. Pair it with use-subscribe on a [:rf.resource/*] query to read the data.

  • descriptor — the resource-instance identity {:resource … :scope … :params …}.
  • opts keys: :cause (recorded on the ensure; defaults to [:lease :mount]) and :frame (pin to an explicit frame-id).
  • Without :frame, frame resolution mirrors use-subscribe, raising :rf.error/no-frame-context when no frame is in scope.
  • Changing the resolved frame, descriptor, or cause releases the old lease and takes a fresh one.
  • The events are inert when the resources artefact is not loaded.
  • Example:
    ;; Hold a liveness lease on a polled resource while the widget is mounted.
    (uix-adapter/use-resource-lease {:resource :my/feed
                                     :scope    :rf.scope/global
                                     :params   {:page 0}})
    

use-current-frame

  • Kind: UIx hook (function)
  • Signature:
    (use-current-frame)  frame-kw, or :rf.frame/no-provider
    
  • Description: Returns the frame keyword supplied by the surrounding frame-provider (SCOPE) or frame-root (ENSURE) — both install the one shared React context this read consults. It exists for components that thread the frame through hand-written child callbacks.

This hook reads the React-context tier only. When neither frame-provider nor frame-root sits above, it returns the no-provider sentinel :rf.frame/no-provider — never nil, and never a synthesised default. It does not consult the with-frame dynamic var; for the full resolution chain, use rf/current-frame-id.

NOT USED — no call sites found in implementation/, examples/, or tools/.

Components

frame-provider

  • Kind: UIx component (function — SCOPE-only)
  • Signature:
    ($ uix-adapter/frame-provider {:frame :session} child)   ;; SCOPE an existing frame
    
  • Description: The UIx-shaped SCOPE-only frame provider (rf2-nyea0r split — roots ensure; providers scope; for create-if-absent, use frame-root). Scopes an already-created frame; creates nothing. Raises:
  • :rf.error/frame-provider-frame-absent when the frame does not exist
  • :rf.error/no-frame-context on a nil :frame
  • :rf.error/bad-frame-provider-arg on a non-keyword :frame
  • :rf.error/frame-provider-given-id when given an :id (the ENSURE key — use frame-root)

Children ride the idiomatic $ trailing-args channel. Pass them after the prop map, as for any other UIx component (there is no :children prop-map key). - Example:

($ uix-adapter/frame-provider {:frame :session}
   ($ dashboard))

frame-root

  • Kind: UIx component (function — ENSURE, a commit-owned two-pass boundary)
  • Signature:
    ($ uix-adapter/frame-root {:id :session :images [session-image]} child)   ;; ENSURE create-if-absent / reuse
    
  • Description: The UIx-shaped ENSURE component (rf2-nyea0r split). Creates the named frame if absent, or reuses it without re-seeding if present; never destroys the frame on unmount. Accepts make-frame opts, including :images / :initial-events. :id must be a keyword; a missing/nil/non-keyword :id raises :rf.error/frame-root-missing-id.
  • Commit-owned two-pass: the create/seed runs in a client useLayoutEffect (at commit), not during render — the first render emits no descendant subtree, and children render only after the frame is live. A render React discards before commit creates + seeds nothing (no ghost frame).
  • Re-mounting under the same :id (hot reload, React StrictMode dev double-invoke) neither destroys durable state nor re-runs :initial-events. A mounted :id/opts change raises :rf.error/frame-root-reconfigured; a stray :frame raises :rf.error/frame-root-given-frame.

Children ride the idiomatic $ trailing-args channel. - Example:

;; create the frame on first mount, seed it once via :initial-events,
;; reuse (no re-seed) on hot-reload re-mount.
($ uix-adapter/frame-root {:id :app :initial-events [[:counter/initialise]]}
   ($ counter-app))

Adapter seams

wrap-view

  • Kind: function
  • Signature:
    (wrap-view id metadata user-fn)  wrapped fn
    
  • Description: Adapter-side source-coord injection: wraps a component head so its rendered root DOM element carries data-rf2-source-coord in debug builds (see the Notes below). Most users register through reg-view*; wrap-view is for code-gen and library scaffolding.
  • Example:
    ;; Code-gen / scaffolding seam: wrap a component head so its root DOM element
    ;; carries data-rf2-source-coord (dev only; elided in production builds).
    (def wrapped-row
      (uix-adapter/wrap-view ::row {:line 42 :column 7}
                             (fn [_props] ($ :div "row"))))
    

flush-views!

  • Kind: function
  • Signature:
    (flush-views!)
    (flush-views! f)
    
  • Description: Wraps React's act() for tests. Flushes pending renders synchronously and returns nil.
  • Example:
    ;; Test-only: flush pending renders synchronously, returns nil.
    (uix-adapter/flush-views!)               ;; 0-arity: drain queued renders + effects
    (uix-adapter/flush-views! (fn [] nil))   ;; 1-arity: run the thunk inside act()
    

set-hiccup-emitter!

  • Kind: function
  • Signature:
    (set-hiccup-emitter! f)
    
  • Description: Install a render-tree → HTML fn. This is the UIx side of the SSR late-bind seam, at parity with the Reagent adapter. Normally you don't call this directly; requiring re-frame.ssr wires the emitter for you. Pass nil to reset.
  • Example:
    ;; SSR: install a render-tree → HTML emitter (normally wired for you by
    ;; requiring re-frame.ssr). Pass nil to reset.
    (uix-adapter/set-hiccup-emitter! (fn [tree _opts] (str tree)))
    (uix-adapter/set-hiccup-emitter! nil)
    

Notes

  • Shared React Context. The frame-provider in all three adapters (Reagent, UIx, Helix) consumes the same createContext object, factored into re-frame.adapter.context (a CLJS-only file in core). There is exactly one Context, not three. A mixed-substrate app therefore composes: a UIx frame-provider can wrap a Reagent or Helix subtree, and vice versa.
  • DOM source-coord annotations. Adapters inject data-rf2-source-coord on every registered view's root element; wrap-view is the explicit seam for that injection. The attribute is gated on debug builds and elided from production :advanced builds via dead-code elimination, so it costs no shipped bytes. It powers click-to-source in Xray and re-frame2-pair. The full contract is in the Observability concept guide.

See also