2016-08-15 01:56:39 +00:00
|
|
|
## Introduction
|
|
|
|
|
|
|
|
This tutorial explains `coeffects`.
|
|
|
|
|
2016-08-16 02:30:01 +00:00
|
|
|
It explains what they are, how they help, how they can be "injected", and how
|
|
|
|
to manage them in tests.
|
2016-08-15 01:56:39 +00:00
|
|
|
|
|
|
|
## Table Of Contexts
|
2016-08-15 08:11:06 +00:00
|
|
|
|
|
|
|
* [What Are They?](#what-are-they-)
|
|
|
|
* [An Example](#an-example)
|
2016-08-16 14:08:30 +00:00
|
|
|
* [How We Want It](#how-we-want-it)
|
2016-08-15 08:11:06 +00:00
|
|
|
* [How Are Coeffect Babies Made?](#how-are-coeffect-babies-made-)
|
|
|
|
* [So, Next Step](#so--next-step)
|
2016-08-16 14:08:30 +00:00
|
|
|
* [`inject-cofx`](#-inject-cofx-)
|
|
|
|
* [More `inject-cofx`](#more--inject-cofx-)
|
2016-08-15 08:11:06 +00:00
|
|
|
* [Meet `reg-cofx`](#meet--reg-cofx-)
|
|
|
|
* [Examples](#examples)
|
2016-08-16 14:08:30 +00:00
|
|
|
* [The 5 Point Summary](#the-5-point-summary)
|
2016-08-15 08:11:06 +00:00
|
|
|
* [Secret Interceptors](#secret-interceptors)
|
|
|
|
* [Testing](#testing)
|
2016-08-15 01:56:39 +00:00
|
|
|
|
|
|
|
## Coeffects
|
|
|
|
|
|
|
|
### What Are They?
|
|
|
|
|
2016-08-16 02:30:01 +00:00
|
|
|
`coeffects` are the data resources that an event handler needs
|
|
|
|
to perform its computation.
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-16 02:30:01 +00:00
|
|
|
Because the majority of event handlers only need `db` and
|
|
|
|
`event`, there's a specific registration function, called `reg-event-db`,
|
2016-08-15 08:11:06 +00:00
|
|
|
which delivers these two coeffects as arguments to an event
|
2016-08-16 02:30:01 +00:00
|
|
|
handler, making this common case easy to program.
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-16 02:30:01 +00:00
|
|
|
But sometimes an event handler needs other data inputs
|
|
|
|
to perform its computation. Things like a random number, or a GUID,
|
2016-08-15 12:54:23 +00:00
|
|
|
or the current datetime. It might even need access to a
|
|
|
|
DataScript connection.
|
2016-08-15 01:56:39 +00:00
|
|
|
|
|
|
|
|
2016-08-15 12:54:23 +00:00
|
|
|
### An Example
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-16 14:08:30 +00:00
|
|
|
This handler obtains data directly from LocalStore:
|
2016-08-15 01:56:39 +00:00
|
|
|
```clj
|
2016-08-16 02:30:01 +00:00
|
|
|
(reg-event-db
|
2016-08-15 08:11:06 +00:00
|
|
|
:load-defaults
|
|
|
|
(fn [coeffects _]
|
2016-08-15 01:56:39 +00:00
|
|
|
(let [val (js->clj (.getItem js/localStorage "defaults-key"))] ;; <-- Problem
|
|
|
|
(assoc db :defaults val))))
|
|
|
|
```
|
|
|
|
|
2016-08-16 14:08:30 +00:00
|
|
|
Because it has directly accessed LocalStore, this event handler is not
|
2016-08-16 02:30:01 +00:00
|
|
|
pure, and impure functions cause well-documented paper cuts.
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-16 02:30:01 +00:00
|
|
|
### How We Want It
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-16 02:30:01 +00:00
|
|
|
Our goal in this tutorial is to rewrite this event handler so
|
2016-08-16 14:08:30 +00:00
|
|
|
that data _only_ comes from the arguments.
|
2016-08-16 02:30:01 +00:00
|
|
|
|
2016-08-16 14:08:30 +00:00
|
|
|
To make this happen, our first change is to switch to
|
|
|
|
using `reg-event-fx` (instead of `reg-event-db`).
|
2016-08-16 02:30:01 +00:00
|
|
|
|
2016-08-16 14:08:30 +00:00
|
|
|
Event handlers registered via `reg-event-fx` are slightly
|
|
|
|
different to those registered via `reg-event-fx`. `-fx` handlers
|
|
|
|
get two arguments, but the first is not `db`. Instead it is an argument which, in
|
|
|
|
this tutorial, we will call `cofx` (that's a nice distinct name which will aid communication).
|
2016-08-16 02:30:01 +00:00
|
|
|
|
|
|
|
Previous tutorials have show us that we can obtain `:db` from
|
2016-08-16 14:08:30 +00:00
|
|
|
`cofx`. Well, in addition we now want it to contain other useful data too.
|
2016-08-15 01:56:39 +00:00
|
|
|
```clj
|
2016-08-16 02:30:01 +00:00
|
|
|
(reg-event-fx ;; note: -fx
|
2016-08-15 08:11:06 +00:00
|
|
|
:load-defaults
|
2016-08-16 02:30:01 +00:00
|
|
|
(fn [cofx event] ;; cofx means coeffects
|
|
|
|
(let [val (:local-store cofx) ;; <-- get data from cofx
|
|
|
|
db (:db cofx)] ;; <-- more data from cofx
|
2016-08-15 01:56:39 +00:00
|
|
|
{:db (assoc db :defaults val))})) ;; returns an effect
|
|
|
|
```
|
|
|
|
|
2016-08-16 14:08:30 +00:00
|
|
|
Notice how `cofx` magically contains a `:local-store` key with the
|
|
|
|
right value. How do we organise for this magic to happen?
|
2016-08-16 02:30:01 +00:00
|
|
|
|
2016-08-16 14:08:30 +00:00
|
|
|
### Abracadabra
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-16 02:30:01 +00:00
|
|
|
Each time an event handler is executed, a brand new `context` is created, and within that
|
2016-08-16 14:08:30 +00:00
|
|
|
`context` is a brand new `:coeffect` map, which is initially totally empty.
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-16 02:30:01 +00:00
|
|
|
That pristine `context` value (containing a pristine `:coeffect` map) is then threaded
|
2016-08-16 14:08:30 +00:00
|
|
|
through a chain of Interceptors before it is finally handled to our event handler,
|
|
|
|
sitting on the end of a chain, itself wrapped up in an interceptor. We know
|
2016-08-15 08:11:06 +00:00
|
|
|
this story well from a previous tutorial.
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-16 02:30:01 +00:00
|
|
|
So, all members of the Interceptor chain have the opportunity to add to `:coeffects`
|
2016-08-16 14:08:30 +00:00
|
|
|
via their `:before` function. This is where `:coeffect` magic happens. This is where
|
|
|
|
new keys can be added to `:coeffect`, so that later our handler magically finds the
|
|
|
|
right data (like `:local-store`) in its `cofx` argument.
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-16 14:08:30 +00:00
|
|
|
### Which Interceptors?
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-16 14:08:30 +00:00
|
|
|
If Interceptors put data in `:coeffect`, then we'll need to add the right ones to
|
|
|
|
our event handler when we register it.
|
2016-08-15 08:11:06 +00:00
|
|
|
|
2016-08-16 14:08:30 +00:00
|
|
|
Something like this (this handler is the same as before, except for one addition):
|
2016-08-15 01:56:39 +00:00
|
|
|
```clj
|
|
|
|
(reg-event-fx
|
2016-08-15 08:11:06 +00:00
|
|
|
:load-defaults
|
2016-08-16 02:30:01 +00:00
|
|
|
[ (inject-cofx :local-store "defaults-key") ] ;; <-- this is new
|
|
|
|
(fn [cofx event]
|
|
|
|
(let [val (:local-store cofx)
|
|
|
|
db (:db cofx)]
|
2016-08-15 01:56:39 +00:00
|
|
|
{:db (assoc db :defaults val))}))
|
|
|
|
```
|
|
|
|
|
2016-08-16 14:08:30 +00:00
|
|
|
So we've added one Interceptor and it will do the magic. It will inject the right
|
|
|
|
value into `context's` `:coeffeects` which then goes on to be the first argument
|
|
|
|
to our event handler (`cofx`)
|
2016-08-15 01:56:39 +00:00
|
|
|
|
|
|
|
|
2016-08-16 02:30:01 +00:00
|
|
|
### `inject-cofx`
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-16 02:30:01 +00:00
|
|
|
`inject-cofx` is part of re-frame API.
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-16 02:30:01 +00:00
|
|
|
It is a function which returns an Interceptor whose `:before` function loads
|
|
|
|
a value into a `context's` `:coeffect` map.
|
|
|
|
|
|
|
|
`inject-cofx` 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.
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-16 02:30:01 +00:00
|
|
|
So, in the case above, the `cofx-id` was `:local-store` and the additional value
|
2016-08-16 14:08:30 +00:00
|
|
|
was "defaults-key" which was presumably the LocalStore key.
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-16 02:30:01 +00:00
|
|
|
### More `inject-cofx`
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-15 08:11:06 +00:00
|
|
|
Here's some other examples of its use:
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-16 02:30:01 +00:00
|
|
|
- `(inject-cofx :random-int 10)`
|
|
|
|
- `(inject-cofx :guid)`
|
|
|
|
- `(inject-cofx :now)`
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-15 08:11:06 +00:00
|
|
|
So, if I wanted to, I could create an event handler which has access to 3 coeffects:
|
|
|
|
```clj
|
|
|
|
(reg-event-fx
|
|
|
|
:some-id
|
2016-08-16 02:30:01 +00:00
|
|
|
[(inject-cofx :random-int 10) (inject-cofx :now) (inject-cofx :local-store "blah")] ;; 3
|
|
|
|
(fn [cofx _]
|
|
|
|
... in here I can access cofx's keys :now :local-store and :random-int))
|
2016-08-15 01:56:39 +00:00
|
|
|
```
|
|
|
|
|
2016-08-16 14:08:30 +00:00
|
|
|
Adding 3 coeffects for the one handler is probably just showing off, and not generally necessary.
|
2016-08-15 12:54:23 +00:00
|
|
|
|
2016-08-16 14:08:30 +00:00
|
|
|
And so, the final piece in the puzzle: how does `inject-cofx` know what to do when
|
2016-08-16 02:30:01 +00:00
|
|
|
it is given `:now` or `:local-store` ? Each `cofx-id` requires a different action.
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-15 08:11:06 +00:00
|
|
|
### Meet `reg-cofx`
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-16 09:00:56 +00:00
|
|
|
Allows you associate a`cofx-id` (like `:now` or `:local-store`) with a
|
|
|
|
handler function that injects the right data.
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-16 09:00:56 +00:00
|
|
|
The function you register will be passed two arguments:
|
2016-08-16 02:30:01 +00:00
|
|
|
- a `:coeffects` map, and
|
2016-08-16 09:00:56 +00:00
|
|
|
- optionally, the additional value supplied
|
2016-08-16 02:30:01 +00:00
|
|
|
and it is expected to return a modified `:coeffects` map, presumably with an
|
|
|
|
added key and value.
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-15 08:11:06 +00:00
|
|
|
### Examples
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-16 02:30:01 +00:00
|
|
|
Above we wrote an event handler that wanted `:now` data to be available. Here
|
|
|
|
is how a handler could be registered for `:now`:
|
2016-08-15 08:11:06 +00:00
|
|
|
```clj
|
2016-08-16 09:00:56 +00:00
|
|
|
(reg-cofx ;; uses this new registration function
|
2016-08-15 08:11:06 +00:00
|
|
|
:now ;; what cofx-id are we registering
|
2016-08-16 09:00:56 +00:00
|
|
|
(fn [coeffects _] ;; second parameter not used in this case
|
|
|
|
(assoc coeffects :now (js.Date.)))) ;; add :now key, with value
|
2016-08-15 08:11:06 +00:00
|
|
|
```
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-16 09:00:56 +00:00
|
|
|
And there's this example:
|
2016-08-15 08:11:06 +00:00
|
|
|
```clj
|
|
|
|
(reg-cofx ;; new registration function
|
|
|
|
:local-store
|
2016-08-16 02:30:01 +00:00
|
|
|
(fn [coeffects local-store-key]
|
2016-08-15 08:11:06 +00:00
|
|
|
(assoc coeffects
|
|
|
|
:local-store
|
2016-08-16 02:30:01 +00:00
|
|
|
(js->clj (.getItem js/localStorage local-store-key))))
|
2016-08-15 08:11:06 +00:00
|
|
|
```
|
2016-08-15 01:56:39 +00:00
|
|
|
|
|
|
|
|
2016-08-16 02:30:01 +00:00
|
|
|
With these two registrations in place, I can now use `(inject-cofx :now)` and
|
|
|
|
`(inject-cofx :local-store "blah")` in an effect handler's interceptor chain.
|
2016-08-15 12:54:23 +00:00
|
|
|
|
2016-08-16 09:00:56 +00:00
|
|
|
### The 5 Point Summary
|
2016-08-15 12:54:23 +00:00
|
|
|
|
2016-08-16 09:00:56 +00:00
|
|
|
In note form:
|
2016-08-15 12:54:23 +00:00
|
|
|
|
|
|
|
1. Event handlers should only source data from their arguments
|
2016-08-16 09:00:56 +00:00
|
|
|
2. We want to "inject" required data into the first, cofx argument
|
|
|
|
3. We use the `(inject-cofx :key)` interceptor in registration of the event handler
|
|
|
|
4. It will the registered cofx handler for that `:key` to do the injection
|
|
|
|
5. We must have previously registered a cofx handler via `reg-cofx`
|
2016-08-15 12:54:23 +00:00
|
|
|
|
2016-08-15 08:11:06 +00:00
|
|
|
|
|
|
|
### Secret Interceptors
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-15 08:11:06 +00:00
|
|
|
In a previous tutorial we learned that `reg-events-db`
|
|
|
|
and `reg-events-fx` add Interceptors to front of any chain during registration.
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-16 02:30:01 +00:00
|
|
|
We found they inserted an Interceptor called `do-fx`. I can now reveal that
|
2016-08-16 09:00:56 +00:00
|
|
|
they also add `(inject-cofx :db)` at the front of each chain.
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-16 09:00:56 +00:00
|
|
|
Guess what that injects into the `:coeffects` of every event handler?
|
|
|
|
|
|
|
|
Okay, so that was the last surprise. Now you know everything. Hopefully
|
|
|
|
the pizzle pieces fit nicely together
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-15 08:11:06 +00:00
|
|
|
### Testing
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-16 09:00:56 +00:00
|
|
|
During testing, you may want to stub out certain coeffets.
|
|
|
|
|
|
|
|
You may, for example, want to test that an event handler works
|
|
|
|
using a specific `random number`.
|
2016-08-15 01:56:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
|