6.2 KiB
Introduction
This tutorial explains coeffects
.
It explains what they are, how they are created, how they help, and how to manage them in tests. There's also an adults-only moment.
Table Of Contexts
Coeffects
What Are They?
coeffects
are the input resources that an event handler requires
to perform its computation. By "resources" I mean "data".
Because the majority of event handlers require only db
and
event
, there's a registration function, called reg-event-db
,
which delivers these two coeffects as arguments to an event
handler, making it easy.
But sometimes an event handler needs other inputs to perform their computation. Things like a random number, or a GUID, or the current datetime. Perhaps it might need access to an in-memory DataScript database.
An Example
This handler obtains data directly from LocalStore
(reg-event-fx
:load-defaults
(fn [coeffects _]
(let [val (js->clj (.getItem js/localStorage "defaults-key"))] ;; <-- Problem
(assoc db :defaults val))))
Because it has accessed LocalStore, this event handler has stopped being pure, which will trigger well documented paper cuts.
Let's Fix It
Here's how we'd like to rewrite that handler. Data should
only come from the coeffects
argument:
(reg-event-fx ;; using -fx registration
:load-defaults
(fn [coeffects event] ;; 1st argument is coeffects
(let [val (:local-store coeffects) ;; <-- get value from argument
db (:db coeffects)]
{:db (assoc db :defaults val))})) ;; returns an effect
Problem solved? Well, yes, the handler is now pure. But we have a
new problem: how do we arrange for the right :local-store
value
to be available in coeffects
.
How Are Coeffect Babies Made?
Well ... when two coeffects love each other very much ... no, stop ... this is a G-rated framework. Instead ...
Every time an event handler is executed, a new context
is created, and within that
context
is a new :coeffect
map, which is initially totally empty.
That pristine context
value is then threaded through a chain of Interceptors, and sitting
on the end of chain is the event handler, wrapped up as the final interceptor. We know
this story well from a previous tutorial.
So all members of the Interceptor chain have the opportunity to add to :coeffects
via their :before
function.
So, Next Step
Armed with that mindset, let's add an interceptor to the registration, which puts the right localstore value into coeffect.
Here's a sketch:
(reg-event-fx
:load-defaults
[ (coeffect :local-store "defaults-key") ] ;; <-- this is new
(fn [coeffects event]
(let [val (:local-store coeffects)
db (:db coeffects)]
{:db (assoc db :defaults val))}))
Problem solved? Well, no. We're still sketching here. What's that coeffects
function returning
and how does it know to load from LocalStore.
coeffect
the function
The coeffect
function is part of re-frame API.
It returns an Interceptor whose :before
function loads a value into a context's
:coeffect
map.
It takes either one or two arguments. The first is always the id
of the coeffect
required (called a cofx-id
). The 2nd is an optional addition value.
So in the case above, the cofx-id
was :local-store
and the additional value was "defaults-key".
Other Example Uses of coeffects
Here's some other examples of its use:
(coeffects :random-int 10)
(coeffects :guid)
(coeffects :now)
So, if I wanted to, I could create an event handler which has access to 3 coeffects:
(reg-event-fx
:some-id
[(coeffects :random-int 10) (coeffects :now) (coeffects :local-store "blah")] ;; 3
(fn [coeffects _]
... in here I can access coeffect's keys :now :local-store and :random-int))
Probably excessive. But we're still missing the final piece. How do we register a coeffect handler so that this function
coeffect
knows what value you want?
Meet reg-cofx
This function allows you associate acofx-id
(like :now
or :local-store
) with a handler function.
The handler function registered for a cofx-id
will be passed a :coeffects
map, and it
is expected to return a modified map, presumably with an added key and value.
Examples
You saw above the use of :now
. He're is how a cofx
handler could be registered:
(reg-cofx ;; using this new registration function
:now ;; what cofx-id are we registering
(fn [coeffects _] ;; second parameter not used in this case
(assoc coeffects :now (js.Date.)))) ;; add :now key, with value to coeffects
And then there's this example:
(reg-cofx ;; new registration function
:local-store
(fn [coeffects lsk] ;; second parameter is the local store key
(assoc coeffects
:local-store
(js->clj (.getItem js/localStorage lsk))))
With these two registrations in place, I can now use (coefect :now)
and
(coeffect :local-store "blah")
in an effect handler's inteerceptor chain.
Secret Interceptors
In a previous tutorial we learned that reg-events-db
and reg-events-fx
add Interceptors to front of any chain during registration.
If you remember, they insert an Interceptor called do-effects
, which ultimately
actions effects, in its :after
function.
I can now reveal that they also add (coeffect :db)
at the front of each chain. (Last surprise, I promise)
Guess what that adds to coeffects?
Testing
re=-frame has very few XXX
This plugable approach,