Skip to content

re-frame.alpha

Registration

reg

(reg kind & args)

Register a handler.

kind: what kind of handler to register. Possible vals:

:sub-lifecycle

UNDOCUMENTED. See https://github.com/day8/re-frame/issues/680.

:legacy-sub

(reg :sub query-id signal computation-fn)

Register a signal-fn and a computation-fn for a given query-id. computation-fn is a function of [signals query-vector]. See :sub for an explanation of all the arguments.

Compatibility:

Call sub to invoke the computation-fn. If passed a query-map, sub converts it to a query-vector.

If a :re-frame/query-v key is present, sub uses it for the query-vector. Otherwise it uses the :re-frame/q key to build a 1-item vector. sub includes the original query-map in the metadata with a :re-frame/query-m key.

For instance, {::rf/q ::items} converts to: ^{::rf/query-m {::rf/q ::items}} [::items]

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{::rf/q ::items
 ::rf/lifecycle :reactive
 ::rf/query-v [::items 1 2 3]}


converts to:

#!clj
^{::rf/query-m {::rf/q ::items
                ::rf/lifecycle :reactive}}
[::items 1 2 3]

:sub

(reg :sub query-id signal computation-fn)

Register a signal-fn and a computation-fn for a given query-id. computation-fn is a function of [signals query-map]. Call sub to invoke the handler. If passed a query-vector, re-frame converts it to a query map, merging in the value of :re-frame/query-m from the metadata, and including the original query-vector with a :re-frame/query-v key.

For instance, ^{:rf/query-m {:rf/lifecycle :reactive}} [::items 1 2 3] converts to:

1
2
3
{::rf/q ::items
 ::rf/lifecycle :reactive
 ::rf/query-v [::items 1 2 3]}

For convenience, re-frame also merges in the :re-frame/lifecycle key from the metadata. For instance, ^{:rf/lifecycle :reactive} [::items 1 2 3] converts to:

1
2
3
{:rf/q ::items
 :rf/lifecycle :reactive
 :rf/query-v [::items 1 2 3]}

A call to (reg :sub query-id signal-fn computation-fn) associates a query-id WITH two functions.

The two functions provide 'a mechanism' for creating a node in the Signal Graph. When a node of type query-id is needed, the two functions can be used to create it.

  • query-id - typically a namespaced keyword (later used in subscribe).
  • signal-fn - optional. Returns the input data-flows required by this kind of node.
  • computation-fn - computes the value (output) of the node (from the input data flows).

Later, during app execution, a call to (sub {:re-frame/q :sub-id :color :blue}) will trigger the need for a new :sub-id Signal Graph node (matching the query {:re-frame/q :sub-id :color :blue}). And, to create that node the two functions associated with :sub-id will be looked up and used.

Just to be clear: calling reg :sub does not immediately create a node. It only registers 'a mechanism' (the two functions) by which nodes can be created later, when a node is bought into existence by the use of sub.

The computation-fn is always the last argument supplied and has three ways to be called. Two of these ways are syntactic sugar to provide easier access to functional abstractions around your data.

  1. A function that will accept two parameters, the input-values and query. This is the standard way to provide a computation-function

    1
    2
    3
    4
    (reg-sub
      :query-id
      (fn [input-values query]
        (:foo input-values)))
    
  2. A single sugary tuple of :->and a 1-arity computation-function:

    1
    2
    3
    (reg-sub
     :query-id
     :-> computation-fn)
    

    This sugary variation allows you to pass a function that will expect only one parameter, namely the input-values, and entirely omit the query. A typical computation-function expects two parameters which can cause unfortunate results when attempting to use clojure standard library functions, or other functions, in a functional manner.

    For example, a significant number of subscriptions exist only to get a value from the input-values. As shown below, this subscription will simply retrieve the value associated with the :foo key in our db:

    1
    2
    3
    4
    (reg-sub
      :query-id
      (fn [db _]    ;; :<---- trivial boilerplate we might want to skip over
        (:foo db)))
    

    This is slightly more boilerplate than we might like to do, as we can use a keyword directly as a function, and we might like to do this:

    1
    2
    3
    (reg-sub
      :query-id
      :foo)  ;; :<---- This could be dangerous. If `:foo` is not in db, we get the `query-vector` instead of `nil`.
    

    By using :-> our function would not contain the query-vector, and any missing keys would be represented as such:

    1
    2
    3
    (reg-sub
      :query-id
      :-> :foo)
    

    This form allows us to ignore the query if our computation-fn has no need for it, and be safe from any accidents. Any 1-arity function can be provided, and for more complicated use cases, partial, comp, and anonymous functions can still be used.

  3. A single sugary tuple of :=> and a multi-arity computation-function

    1
    2
    3
    (reg-sub
      :query-id
      :=> computation-fn)
    

    A vector query can be broken into two components [query-id & optional-values], and some subscriptions require the optional-values for extra work within the subscription. To use them in variation #1, we need to destructure our computation-fn parameters in order to use them.

    1
    2
    3
    4
    (reg-sub
      :query-id
      (fn [db [_ foo]]
        [db foo]))
    

    Again we are writing boilerplate just to reach our values, and we might prefer to have direct access through a parameter vector like [input-values optional-values] instead, so we might be able to use a multi-arity function directly as our computation-fn. A rewrite of the above sub using this sugary syntax would look like this:

    1
    2
    3
    (reg-sub
      :query-id
      :=> vector)  ;; :<---- Could also be `(fn [db foo] [db foo])`
    

    If handling a map query, :=> simply uses the entire map.

The computation function is expected to take two arguments:

  • input-values - the values which flow into this node (how is it wired into the graph?)
  • query - the value passed to sub

and it returns a computed value (which becomes the output of the node)

When computation-fn is called, the 2nd argument, query, will be that value passed to sub. So, if the call was (subscribe {:re-frame/q :sub-id :color :blue}), then the query supplied to the computation function will be {:re-frame/q :sub-id :color :blue}.

The argument(s) supplied to reg-sub between query-id and the computation-function can vary in 3 ways, but whatever is there defines the input signals part of the mechanism, specifying what input values "flow into" the computation function (as the 1st argument) when it is called.

So, reg-sub can be called in one of three ways, because there are three ways to define the input signals part. But note, the 2nd way, in which a signals function is explicitly supplied, is the most canonical and instructive. The other two are really just sugary variations.

First variation - no input signal function given:

1
2
3
(reg-sub
  :query-id
  a-computation-fn)   ;; has signature:  (fn [db query]  ... ret-value)

In the absence of an explicit signals function, the node's input signal defaults to app-db and, as a result, the value within app-db (a map) is given as the 1st argument when a-computation-fn is called.

Second variation - a signal function is explicitly supplied:

  #!clj
  (reg-sub
    :query-id
    signal-fn     ;; <-- here
    computation-fn)

This is the most canonical and instructive of the three variations.

When a node is created from the template, the signal function will be called and it is expected to return the input signal(s) as either a singleton, if there is only one, or a sequence if there are many, or a map with the signals as the values.

The current values of the returned signals will be supplied as the 1st argument to the a-computation-fn when it is called - and subject to what this signal-fn returns, this value will be either a singleton, sequence or map of them (paralleling the structure returned by the signal function).

This example signal function returns a 2-vector of input signals.

  #!clj
  (fn [query]
     [(sub [:a-sub])
      (sub [:b-sub])])

The associated computation function must be written to expect a 2-vector of values for its first argument:

  #!clj
  (fn [[a b] query]     ;; 1st argument is a seq of two values
    ....)

If, on the other hand, the signal function was simpler and returned a singleton, like this:

  #!clj
  (fn [query]
    (sub [:a-sub]))      ;; <-- returning a singleton

then the associated computation function must be written to expect a single value as the 1st argument:

  #!clj
  (fn [a query]       ;; 1st argument is a single value
     ...)

Further Note: variation #1 above, in which an signal-fn was not supplied, like this:

  #!clj
  (reg-sub
    :query-id
    a-computation-fn)   ;; has signature:  (fn [db query]  ... ret-value)

is the equivalent of using this 2nd variation and explicitly supplying a signal-fn which returns app-db:

  #!clj
  (reg-sub
    :query-id
    (fn [_ _] re-frame/app-db)   ;; <-- explicit signal-fn
    a-computation-fn)             ;; has signature:  (fn [db query-vec]  ... ret-value)

Third variation - syntax Sugar

  #!clj
  (reg-sub
    :a-b-sub
    :<- [:a-sub]
    :<- [:b-sub]
    (fn [[a b] query]    ;; 1st argument is a seq of two values
      {:a a :b b}))

This 3rd variation is just syntactic sugar for the 2nd. Instead of providing an signals-fn you provide one or more pairs of :<- and a subscription vector.

If you supply only one pair a singleton will be supplied to the computation function, as if you had supplied a signal-fn returning only a single value:

  #!clj
  (reg-sub
    :a-sub
    :<- [:a-sub]
    (fn [a query]      ;; only one pair, so 1st argument is a single value
      ...))

Syntactic sugar for both the signal-fn and computation-fn can be used together and the direction of arrows shows the flow of data and functions. The example from directly above is reproduced here:

  #!clj
  (reg-sub
    :a-b-sub
    :<- [:a-sub]
    :<- [:b-sub]
    :-> (partial zipmap [:a :b]))

For further understanding, read the tutorials, and look at the detailed comments in /examples/todomvc/src/subs.cljs.

See also: sub

Subscription

sub

(sub q)

(sub id q)

Given a query, returns a Reagent reaction which will, over time, reactively deliver a stream of values. So, in FRP-ish terms, it returns a Signal.

To obtain the current value from the Signal, it must be dereferenced:

1
2
3
(let [signal (sub {:re-frame/q ::items})
      value  (deref signal)]     ;; could be written as @signal
  ...)

which is typically written tersely as simple:

1
2
(let [items  @(sub {:re-frame/q ::items})]
  ...)

query is a map containing: :re-frame/q: Required. Names the query. Typically a namespaced keyword. :re-frame/lifecycle: Optional. See docs for reg-sub-lifecycle.

The entire query is passed to the subscription handler. This means you can use additional keys to parameterise the query it performs.

Example Usage:

1
2
3
4
5
(require '[re-frame :as-alias rf])
(sub {::rf/q ::items
            ::rf/lifecycle ::rf/reactive
            :color "blue"
            :size :small})

Note: for any given call to sub there must have been a previous call toreg, registering the query handler (functions) associated withquery-id`.

De-duplication

Two, or more, concurrent subscriptions for the same query will source reactive updates from the one executing handler.

See also: reg-sub

Flows

Flows have their own lifecycle, and you don't need to provide an ::rf/lifecycle key. To subscribe to a flow, simply call:

1
(sub :flow {:id :your-flow-id})

Legacy support

dyn-v is not supported.

Flows

reg-flow

(reg-flow flow)

(reg-flow id flow)

Registers a flow.

A full tutorial can be found at https://day8.github.io/re-frame/Flows

Re-frame uses the flow registry to execute a dataflow graph.

On every event, re-frame runs each registered flow. It resolves the flow's inputs, determines if the flow is live, and if so, evaluates the output function, putting the result in app-db at the path.

A flow is a map, specifying one dataflow node. It has keys:

  • :id: uniquely identifies the node.
  • When a flow is already registered with the same :id, replaces it.
  • You can provide an id argument to reg-flow, instead of including :id.
  • :inputs: a map of keyword->input. An input can be one of two types:
  • vector: expresses a path in app-db.
  • map: expresses the output of another flow, identified by a ::re-frame.flow.alpha/flow<- key. Call the re-frame.alpha/flow<- function to construct this map.
  • :output: a function of the keyword->resolved-input map, returning the output value of the node.
  • A resolved vector input is the value in app-db at that path.
  • A resolved flow<- input is the value in app-db at the path of the named flow.
  • Re-frame topologically sorts the flows, to make sure any input flows always run first.
  • Re-frame throws an error at registration time if any flow inputs form a cycle.
  • :path: specifies the app-db location where the :output value is stored.
  • :live-inputs: a map of keyword->live-input for the :live? function.
  • A live-input works the same way an input.
  • :live?: a predicate function of the keyword->resolved-live-input map, returning the current lifecycle state of the node.
  • :cleanup: a function of app-db and the :path.
  • Returns a new app-db.
  • Runs the first time :live? returns false
  • Runs when the flow is cleared (see re-frame.alpha/clear-flow).

:id is the only required key. All others have a default value:

  • :path: [id]
  • :inputs: {}
  • :output: (constantly true)
  • :live?: (constantly true)
  • :live-inputs: {}
  • :cleanup: re-frame.utils/dissoc-in

clear-flow

(clear-flow)

(clear-flow id)

Arguments: [id] Deregisters a flow, identified by id.

Later, re-frame will update app-db with the flow's :cleanup function.

If clear-flow is invoked by the :clear-flow effect, this cleanup happens in the :after phase of the same event returning :clear-flow.

If you call clear-flow directly, cleanup will happen on the next event.

get-flow

(get-flow db id)

Returns the value within db at the :path given by the registered flow with an :id key equal to id, if it exists. Otherwise, returns nil.

flow<-

(flow<- id)

Creates an input from a flow id.

Legacy Compatibility

subscribe

Equivalent to sub (except with flows, which have their own lifecycle and are not cached).

Call (subscribe [:flow {:id :your-flow-id}]) to subscribe to a flow.

reg-sub

(reg-sub & args)

Equivalent to reg :legacy-sub.