5.5 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 an adults-only moment.
Table Of Contexts
Coeffects
What Are They?
coeffects
are the input resources required by an event handler
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 inputs 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 is obtaining data directly from LocalStore
(reg-event-fx
:load-localstore
(fn [coeffect _]
(let [val (js->clj (.getItem js/localStorage "defaults-key"))] ;; <-- Problem
(assoc db :defaults val))))
Because this event handler has directly accessed a resources, it has stopped being pure, causing well documented problems to to accrue.
Let's Fix It Up
Here's how we'd like to write it. Data should only come from the coeffects argument:
(reg-event-fx ;; using -fx registration
:load-localstore
(fn [coeffects event] ;; 1st argument is coeffects
(let [val (:local-store coeffects)
db (:db coeffects)] ;; <-- get value from argument
{:db (assoc db :defaults val))})) ;; returns an effect
Problem solved?
Well, no. We must still arrange for the right LocalStore value to be put
into coeffects.
How Are Coeffect Babies Made?
Awkward moment, but it is now time for THAT talk. Err, hurm. When two coeffects love each other very much ... no, stop ... it doesn't happen that way. 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 handler, wrapped up as the final interceptor. We know
this story well from a previous tutorials.
So it becomes obvious that Interceptors put coeffects in place. Or more specifically, the
:before
functions of Interceptors.
So, Next Step
So now we now tweak our localstore-consuming event handler in one way:
(reg-event-fx
:load-localstore
[ (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, not yet. What's this coeffects
function returning and how does it know to load LocalStore.
coeffect
the function
coeffect
is a part of re-frame.
It returns an Interceptor whos :before
fucntion loads a value into a context's
:coeffect
.
It takes either one or two arguments. The first is always the id
of the coeffect required (called a cofx-id
). The 2nd is optional
addition value.
So in the case above, the cofx-id
was :local-store
and the additional calue was "defaults-key".
Other Uses of coeffects
Here's a couple of other examples:
(coeffects :random-int 10)
(coeffects :guid)
In a previous tutorial we also learned that reg-events-db
and reg-events-fx
add Interceptors to front of any chain during registration. They insert an Interceptor
called do-effects
, which ultimately actions effects.
And it turns out that they also add Interceptors which cause the value of app-db
to be placed in coeffects
. They do it by adding this to any events Interceptor chain:
(coeffects :db)
coeffects
is a function, which takes one parameter, and that's the "coeffects handler"
which will summon the right value. The function returns an Interceptor.
Okay, let me explain that a bit more. I've gone a bit fast.
Notes:
(coeffects {:something value})
or{coeffects :something)
will look up acofx
handler for:something
- An Interceptor is returned from the
- It will call this handler passing in value.
- It will take the returned value and add it to the
:coffect
of the context under the key.
So it will come as no surprise to you to learn that it is Interceptors which
fill :coeffects
with "necessary inputs".
The :coeffect
might start pristine and empty, but it is threaded through the
:before
functions of each Interceptor in the chain prior to the handler
interceptor sitting at the end.
But which Interceptors?
We saw in a previous tutorial that both . At the time, also add another interceptor and it looks like this:
(coeffects :db)
So that interceptor is added to every one of your registered event handlers. And it
adds :db
to the coeffect
.
So how does (coffects :db
)` work? How could I create my own.
From previous tutorials we are familiar with reg-event-db
and reg-event-fx
add
their own Interceptors to any chain you provide. Well, one of the interceptors XXX
XXXX
specifically a map of data. the inputs required by an event handler.
At a more pragmatic level, coeffects is a map in context
How
An Interceptor factory called coeffect
reg-cofx
Examples
XXX need a random number???
Testing
It immedaitely becomes harder to test, etc.