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 |
|
: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 |
|
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 |
|
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.
-
A function that will accept two parameters, the
input-values
andquery
. This is the standard way to provide acomputation-function
1 2 3 4
(reg-sub :query-id (fn [input-values query] (:foo input-values)))
-
A single sugary tuple of
:->
and a 1-aritycomputation-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 thequery
. A typicalcomputation-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 thequery-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 ourcomputation-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. -
A single sugary tuple of
:=>
and a multi-aritycomputation-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 theoptional-values
for extra work within the subscription. To use them in variation #1, we need to destructure ourcomputation-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 ourcomputation-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 tosub
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 |
|
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 |
|
which is typically written tersely as simple:
1 2 |
|
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 |
|
Note: for any given call to sub there must have been a previous call
to
reg, registering the query handler (functions) associated with
query-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 |
|
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 toreg-flow
, instead of including:id
. :inputs
: a map ofkeyword->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 there-frame.alpha/flow<-
function to construct this map. :output
: a function of thekeyword->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 inapp-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 theapp-db
location where the:output
value is stored.:live-inputs
: a map ofkeyword->live-input
for the:live?
function.- A
live-input
works the same way aninput
. :live?
: a predicate function of thekeyword->resolved-live-input
map, returning the current lifecycle state of the node.:cleanup
: a function ofapp-db
and the:path
.- Returns a new
app-db
. - Runs the first time
:live?
returnsfalse
- 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
.