2016-08-15 01:56:39 +00:00
|
|
|
## Introduction
|
|
|
|
|
|
|
|
This tutorial explains `coeffects`.
|
|
|
|
|
|
|
|
It explains what they are, how they are created, how they help, and how
|
2016-08-15 08:11:06 +00:00
|
|
|
to manage them in tests. There's also an adults-only moment.
|
2016-08-15 01:56:39 +00:00
|
|
|
|
|
|
|
## Table Of Contexts
|
2016-08-15 08:11:06 +00:00
|
|
|
|
|
|
|
- [Introduction](#introduction)
|
|
|
|
- [Table Of Contexts](#table-of-contexts)
|
|
|
|
- [Coeffects](#coeffects)
|
|
|
|
* [What Are They?](#what-are-they-)
|
|
|
|
* [An Example](#an-example)
|
|
|
|
* [Let's Fix It](#let-s-fix-it)
|
|
|
|
* [How Are Coeffect Babies Made?](#how-are-coeffect-babies-made-)
|
|
|
|
* [So, Next Step](#so--next-step)
|
|
|
|
* [`coeffect` the function](#-coeffect--the-function)
|
|
|
|
* [Other Example Uses of `coeffects`](#other-example-uses-of--coeffects-)
|
|
|
|
* [Meet `reg-cofx`](#meet--reg-cofx-)
|
|
|
|
* [Examples](#examples)
|
|
|
|
* [Secret Interceptors](#secret-interceptors)
|
|
|
|
* [Testing](#testing)
|
2016-08-15 01:56:39 +00:00
|
|
|
|
|
|
|
## Coeffects
|
|
|
|
|
|
|
|
### What Are They?
|
|
|
|
|
2016-08-15 08:11:06 +00:00
|
|
|
`coeffects` are the input resources that an event handler requires
|
2016-08-15 01:56:39 +00:00
|
|
|
to perform its computation. By "resources" I mean "data".
|
|
|
|
|
|
|
|
Because the majority of event handlers require only `db` and
|
2016-08-15 08:11:06 +00:00
|
|
|
`event`, there's a registration function, called `reg-event-db`,
|
|
|
|
which delivers these two coeffects as arguments to an event
|
2016-08-15 01:56:39 +00:00
|
|
|
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
|
|
|
|
|
2016-08-15 08:11:06 +00:00
|
|
|
This handler obtains data directly from LocalStore
|
2016-08-15 01:56:39 +00:00
|
|
|
```clj
|
|
|
|
(reg-event-fx
|
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-15 08:11:06 +00:00
|
|
|
Because it has accessed LocalStore, this event handler has stopped being
|
|
|
|
pure, which will trigger well documented paper cuts.
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-15 08:11:06 +00:00
|
|
|
### Let's Fix It
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-15 08:11:06 +00:00
|
|
|
Here's how __we'd like to rewrite that handler__. Data should
|
|
|
|
only come from the `coeffects` argument:
|
2016-08-15 01:56:39 +00:00
|
|
|
```clj
|
|
|
|
(reg-event-fx ;; using -fx registration
|
2016-08-15 08:11:06 +00:00
|
|
|
:load-defaults
|
2016-08-15 01:56:39 +00:00
|
|
|
(fn [coeffects event] ;; 1st argument is coeffects
|
2016-08-15 08:11:06 +00:00
|
|
|
(let [val (:local-store coeffects) ;; <-- get value from argument
|
|
|
|
db (:db coeffects)]
|
2016-08-15 01:56:39 +00:00
|
|
|
{:db (assoc db :defaults val))})) ;; returns an effect
|
|
|
|
```
|
|
|
|
|
2016-08-15 08:11:06 +00:00
|
|
|
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`.
|
2016-08-15 01:56:39 +00:00
|
|
|
|
|
|
|
### How Are Coeffect Babies Made?
|
|
|
|
|
2016-08-15 08:11:06 +00:00
|
|
|
Well ... when two coeffects love each other very much ... no, stop ... this
|
|
|
|
is a G-rated framework. Instead ...
|
2016-08-15 01:56:39 +00:00
|
|
|
|
|
|
|
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
|
2016-08-15 08:11:06 +00:00
|
|
|
on the end of chain is the event handler, wrapped up as the final interceptor. We know
|
|
|
|
this story well from a previous tutorial.
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-15 08:11:06 +00:00
|
|
|
So all members of the Interceptor chain have the opportunity to add to `:coeffects`
|
|
|
|
via their `:before` function.
|
2016-08-15 01:56:39 +00:00
|
|
|
|
|
|
|
### So, Next Step
|
|
|
|
|
2016-08-15 08:11:06 +00:00
|
|
|
Armed with that mindset, let's add an interceptor to the registration, which
|
|
|
|
puts the right localstore value into coeffect.
|
|
|
|
|
|
|
|
Here's a sketch:
|
2016-08-15 01:56:39 +00:00
|
|
|
```clj
|
|
|
|
(reg-event-fx
|
2016-08-15 08:11:06 +00:00
|
|
|
:load-defaults
|
2016-08-15 01:56:39 +00:00
|
|
|
[ (coeffect :local-store "defaults-key") ] ;; <-- this is new
|
|
|
|
(fn [coeffects event]
|
|
|
|
(let [val (:local-store coeffects)
|
|
|
|
db (:db coeffects)]
|
|
|
|
{:db (assoc db :defaults val))}))
|
|
|
|
```
|
|
|
|
|
2016-08-15 08:11:06 +00:00
|
|
|
Problem solved? Well, no. We're still sketching here. What's that `coeffects` function returning
|
|
|
|
and how does it know to load from LocalStore.
|
2016-08-15 01:56:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
### `coeffect` the function
|
|
|
|
|
2016-08-15 08:11:06 +00:00
|
|
|
The `coeffect` function is part of re-frame API.
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-15 08:11:06 +00:00
|
|
|
It returns an Interceptor whose `:before` function loads a value into a `context's` `:coeffect` map.
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-15 08:11:06 +00:00
|
|
|
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.
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-15 08:11:06 +00:00
|
|
|
So in the case above, the `cofx-id` was `:local-store` and the additional value was "defaults-key".
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-15 08:11:06 +00:00
|
|
|
### Other Example Uses of `coeffects`
|
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
|
|
|
|
|
|
|
- `(coeffects :random-int 10)`
|
|
|
|
- `(coeffects :guid)`
|
2016-08-15 08:11:06 +00:00
|
|
|
- `(coeffects :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
|
|
|
|
[(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))
|
2016-08-15 01:56:39 +00:00
|
|
|
```
|
|
|
|
|
2016-08-15 08:11:06 +00:00
|
|
|
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?
|
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-15 08:11:06 +00:00
|
|
|
This function allows you associate a`cofx-id` (like `:now` or `:local-store`) with a handler function.
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-15 08:11:06 +00:00
|
|
|
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.
|
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-15 08:11:06 +00:00
|
|
|
You saw above the use of `:now`. He're is how a `cofx` handler could be registered:
|
|
|
|
```clj
|
|
|
|
(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
|
|
|
|
```
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-15 08:11:06 +00:00
|
|
|
And then there's this example:
|
|
|
|
```clj
|
|
|
|
(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))))
|
|
|
|
```
|
2016-08-15 01:56:39 +00:00
|
|
|
|
|
|
|
|
2016-08-15 08:11:06 +00:00
|
|
|
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
|
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-15 08:11:06 +00:00
|
|
|
If you remember, they insert an Interceptor called `do-effects`, which ultimately
|
|
|
|
actions effects, in its `:after` function.
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-15 08:11:06 +00:00
|
|
|
I can now reveal that they also add `(coeffect :db)` at the front of each chain. (Last surprise, I promise)
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-15 08:11:06 +00:00
|
|
|
Guess what that adds to coeffects?
|
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-15 08:11:06 +00:00
|
|
|
re=-frame has very few XXX
|
2016-08-15 01:56:39 +00:00
|
|
|
|
2016-08-15 08:11:06 +00:00
|
|
|
This plugable approach,
|
2016-08-15 01:56:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
|