198 lines
5.5 KiB
Markdown
198 lines
5.5 KiB
Markdown
## 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
|
|
```clj
|
|
(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:
|
|
```clj
|
|
(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:
|
|
```clj
|
|
(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:
|
|
|
|
1. `(coeffects {:something value})` or `{coeffects :something)` will look up a `cofx` handler for `:something`
|
|
2. An Interceptor is returned from the
|
|
2. It will call this handler passing in value.
|
|
3. 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.
|
|
|