re-frame/docs/coeffects.md

192 lines
6.2 KiB
Markdown
Raw Normal View History

## 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.
## 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)
## Coeffects
### What Are They?
2016-08-15 08:11:06 +00:00
`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
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
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
```clj
(reg-event-fx
2016-08-15 08:11:06 +00:00
:load-defaults
(fn [coeffects _]
(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 08:11:06 +00:00
### Let's Fix It
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:
```clj
(reg-event-fx ;; using -fx registration
2016-08-15 08:11:06 +00:00
:load-defaults
(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)]
{: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`.
### 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 ...
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 08:11:06 +00:00
So all members of the Interceptor chain have the opportunity to add to `:coeffects`
via their `:before` function.
### 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:
```clj
(reg-event-fx
2016-08-15 08:11:06 +00:00
: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))}))
```
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.
### `coeffect` the function
2016-08-15 08:11:06 +00:00
The `coeffect` function is part of re-frame API.
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 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 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 08:11:06 +00:00
### Other Example Uses of `coeffects`
2016-08-15 08:11:06 +00:00
Here's some other examples of its use:
- `(coeffects :random-int 10)`
- `(coeffects :guid)`
2016-08-15 08:11:06 +00:00
- `(coeffects :now)`
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 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 08:11:06 +00:00
### Meet `reg-cofx`
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 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 08:11:06 +00:00
### Examples
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 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 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 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 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 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 08:11:06 +00:00
Guess what that adds to coeffects?
2016-08-15 08:11:06 +00:00
### Testing
2016-08-15 08:11:06 +00:00
re=-frame has very few XXX
2016-08-15 08:11:06 +00:00
This plugable approach,