mirror of
https://github.com/status-im/re-frame.git
synced 2025-02-22 23:08:20 +00:00
More docs work
This commit is contained in:
parent
0514e06d0c
commit
48faedb082
@ -1,4 +1,4 @@
|
||||
## Introduction
|
||||
## Effects
|
||||
|
||||
About 10% of the time, event handlers need to cause side effects.
|
||||
|
||||
@ -21,26 +21,24 @@ make side effects a noop in event replays.
|
||||
* [What Makes This Work?](#what-makes-this-work-)
|
||||
* [Order Of Effects?](#order-of-effects-)
|
||||
* [Effects With No Data](#effects-with-no-data)
|
||||
* [Noops](#noops)
|
||||
- [Builtin Effect Handlers](#builtin-effect-handlers)
|
||||
* [Testing And Noops](#testing-and-noops)
|
||||
* [Builtin Effect Handlers](#builtin-effect-handlers)
|
||||
+ [:dispatch-later](#-dispatch-later)
|
||||
+ [:dispatch](#-dispatch)
|
||||
+ [:dispatch-n](#-dispatch-n)
|
||||
+ [:deregister-event-handler](#-deregister-event-handler)
|
||||
+ [:db](#-db)
|
||||
- [External Effects](#external-effects)
|
||||
|
||||
## Effects
|
||||
### Where Effects Come From
|
||||
|
||||
When an event handler is registered via `reg-event-fx`, it always returns effects.
|
||||
When an event handler is registered via `reg-event-fx`, it must return effects.
|
||||
|
||||
Like this:
|
||||
```clj
|
||||
(reg-event-fx ;; -fx registration, not -db registration
|
||||
:my-event
|
||||
(fn [coeffects [_ a]] ;; 1st argument is coeffects, instead of db
|
||||
{:db (assoc (:db coeffects) :flag a)
|
||||
(fn [cofx [_ a]] ;; 1st argument is coeffects, instead of db
|
||||
{:db (assoc (:db cofx) :flag a)
|
||||
:dispatch [:do-something-else 3]})) ;; return effects
|
||||
```
|
||||
|
||||
@ -52,7 +50,7 @@ An effects map contains instructions.
|
||||
|
||||
Each key/value pair in the map is one instruction - the `key` uniquely identifies
|
||||
the particular side effect required, and the `value` for that `key` provides
|
||||
further data. The structure of the `value` is different for each side-effect.
|
||||
further data. The structure of `value` is different for each side-effect.
|
||||
|
||||
Here's the two instructions from the example above:
|
||||
```cljs
|
||||
@ -60,10 +58,10 @@ Here's the two instructions from the example above:
|
||||
:dispatch [:do-something-else 3]} ;; dispatch this event
|
||||
```
|
||||
|
||||
That `:db` `key` instructs that "app-db" should be `reset!` to the
|
||||
`value` supplied for the `key`.
|
||||
The `:db` `key` instructs that "app-db" should be `reset!` to the
|
||||
`value` supplied.
|
||||
|
||||
That `:dispatch` `key` instructs that an event should be
|
||||
And the `:dispatch` `key` instructs that an event should be
|
||||
dispatched. The `value` is the vector to dispatch.
|
||||
|
||||
There's many other possible
|
||||
@ -76,7 +74,7 @@ And so on. And so on. Which brings us to a problem.
|
||||
While re-frame supplies a number of builtin effects, the set of
|
||||
possible effects is open ended.
|
||||
|
||||
What if you use Postgress and want an effect which issues mutating
|
||||
What if you use PostgreSQL and want an effect which issues mutating
|
||||
queries? Or what if you want to send logs to Logentries or metrics to DataDog.
|
||||
Or write values to windows.location. And what happens if your database is
|
||||
X, Y or Z?
|
||||
@ -107,10 +105,10 @@ the key `:butterfly`, the function we are registering will be used to action it.
|
||||
__<2>__ the function which actions the side effect. Later, it will be called
|
||||
with one argument - the value in the effects map, for this key.
|
||||
|
||||
So, if an event handler returned these effects:
|
||||
So, if an event handler returned these two effects:
|
||||
```clj
|
||||
{:dispatch [:save-maiden 42]
|
||||
:butterfly "Flapping"} ;; butterfly effect !!
|
||||
:butterfly "Flapping"} ;; butterfly effect, but no chaos !!
|
||||
```
|
||||
|
||||
Then the function we registered for `:butterfly` would be called to handle
|
||||
@ -118,12 +116,15 @@ that effect. And it would be called with the parameter "Flapping".
|
||||
|
||||
So, terminology:
|
||||
- `:butterfly` is an "effect key"
|
||||
- and the function registered is an "effect handler"
|
||||
- and the function registered is an "effect handler".
|
||||
|
||||
So re-frame has `event handlers` and `effect handlers` and they are
|
||||
different, despite both starting with `e` and ending in `t`!!
|
||||
|
||||
### Writing An Effect Handler
|
||||
|
||||
A word of advice - make them as simple as possible, and then
|
||||
simplify them further. You don't want them containing any fancy logic.
|
||||
simplify them further. You don't want them containing any fancy logic.
|
||||
|
||||
Why? Well, because they are all side-effecty they will be a pain
|
||||
to test rigorously. And the combination of fancy logic and limited
|
||||
@ -148,21 +149,20 @@ In my defence, here's the builtin effect handler for `:db`:
|
||||
(reset! re-frame.db/app-db value)))
|
||||
```
|
||||
|
||||
So, yeah, simple.
|
||||
So, yeah, simple ... and, because of it, I an almost guarantee there's no bug in ... bang, crash, smoke, flames.
|
||||
|
||||
> Note: the return value of an effect handler is ignored.
|
||||
|
||||
### :db Not Always Needed
|
||||
|
||||
An effects map does not have to include the `effect key` `:db`.
|
||||
An effects map does not need to include the `effect key` `:db`.
|
||||
|
||||
It is perfectly reasonable and valid for an event handler
|
||||
to return effects which do not include an update to `app-db`.
|
||||
It is perfectly valid for an event handler
|
||||
to not change `app-db`.
|
||||
|
||||
In fact, it is perfectly okay for an event handler to return
|
||||
In fact, it is perfectly valid for an event handler to return
|
||||
an effects map of `{}`. Slightly puzzling, but not a problem.
|
||||
|
||||
|
||||
### What Makes This Work?
|
||||
|
||||
A silently inserted interceptor.
|
||||
@ -186,13 +186,15 @@ While it might look like you have registered with 2 interceptors,
|
||||
[do-fx debug (path :right)]
|
||||
```
|
||||
|
||||
It silently inserts `do-fx` at the front, and this is a good thing.
|
||||
|
||||
The placement of `do-fx` at the beginning of the interceptor chain means
|
||||
it's `:after` function would be the final act when the chain is executed
|
||||
(forwards and then backwards, as described in the Interceptor Tutorial).
|
||||
|
||||
In this final act, the `:after` function extracts `:effects` from `context`
|
||||
and simply iterates across the key/value pairs it contains, calling the
|
||||
registered effect handlers for each.
|
||||
registered "effect handlers" for each.
|
||||
|
||||
> For the record, the FISA Court requires that we deny all claims
|
||||
> that `do-fx` is secretly injected NSA surveillance-ware. <br>
|
||||
@ -247,10 +249,10 @@ The associated effect handler would look like:
|
||||
(.exitFullscreen js/document)))
|
||||
```
|
||||
|
||||
### Noops
|
||||
### Testing And Noops
|
||||
|
||||
When you are running tests or replaying events, it is sometimes
|
||||
useful to stub out effects.
|
||||
useful to stub out effects.
|
||||
|
||||
This is easily done - you simply register a noop effect handler.
|
||||
|
||||
@ -261,7 +263,24 @@ Want to stub out the `:dispatch` effect? Do this:
|
||||
(fn [_] )) ;; a noop
|
||||
```
|
||||
|
||||
## Builtin Effect Handlers
|
||||
If your test does alter registered effect handlers, and you are using `cljs.test`,
|
||||
then you can use a `fixture` to restore all effect handlers at the end of your test:
|
||||
```clj
|
||||
(defn re-frame-fixture
|
||||
[f]
|
||||
(let [restore-re-frame-fn (re-frame.core/make-restore-fn)]
|
||||
(try
|
||||
(f)
|
||||
(finally (restore-re-frame-fn)))))
|
||||
|
||||
(cljs.test/use-fixtures :each re-frame-fixture)
|
||||
```
|
||||
|
||||
`re-frame.core/make-restore-fn` creates a checkpoint for re-frame state (including
|
||||
registered handlers) to which you can return.
|
||||
|
||||
|
||||
### Builtin Effect Handlers
|
||||
|
||||
#### :dispatch-later
|
||||
|
||||
@ -317,7 +336,7 @@ usage:
|
||||
{:db {:key1 value1 key2 value2}}
|
||||
```
|
||||
|
||||
## External Effects
|
||||
### External Effects
|
||||
|
||||
- https://github.com/Day8/re-frame-http-fx (GETs and POSTs)
|
||||
- https://github.com/Day8/re-frame-forward-events-fx (slightly exotic)
|
||||
|
@ -1,4 +1,4 @@
|
||||
## Introduction
|
||||
## Coeffects
|
||||
|
||||
This tutorial explains `coeffects`.
|
||||
|
||||
@ -15,10 +15,11 @@ to manage them in tests.
|
||||
* [`inject-cofx`](#-inject-cofx-)
|
||||
* [More `inject-cofx`](#more--inject-cofx-)
|
||||
* [Meet `reg-cofx`](#meet--reg-cofx-)
|
||||
* [Examples](#examples)
|
||||
* [The 5 Point Summary](#the-5-point-summary)
|
||||
* [Example Of `reg-cofx`](#example-of--reg-cofx-)
|
||||
* [Another Example Of `reg-cofx`](#another-example-of--reg-cofx-)
|
||||
* [Secret Interceptors](#secret-interceptors)
|
||||
* [Testing](#testing)
|
||||
* [The 5 Point Summary](#the-5-point-summary)
|
||||
|
||||
## Coeffects
|
||||
|
||||
@ -44,7 +45,7 @@ This handler obtains data directly from LocalStore:
|
||||
```clj
|
||||
(reg-event-db
|
||||
:load-defaults
|
||||
(fn [coeffects _]
|
||||
(fn [db _]
|
||||
(let [val (js->clj (.getItem js/localStorage "defaults-key"))] ;; <-- Problem
|
||||
(assoc db :defaults val))))
|
||||
```
|
||||
@ -69,7 +70,7 @@ is an argument which we will call `cofx` (that's a nice distinct
|
||||
name which will aid communication).
|
||||
|
||||
Previous tutorials showed there's a `:db` key in `cofx`. We
|
||||
now want `cofx` to have other keys and values:
|
||||
now want `cofx` to have other keys and values, like this:
|
||||
```clj
|
||||
(reg-event-fx ;; note: -fx
|
||||
:load-defaults
|
||||
@ -80,7 +81,7 @@ now want `cofx` to have other keys and values:
|
||||
```
|
||||
|
||||
Notice how `cofx` magically contains a `:local-store` key with the
|
||||
right value. How do we make this magic happen?
|
||||
right value. Nice! But how do we make this magic happen?
|
||||
|
||||
### Abracadabra
|
||||
|
||||
@ -88,18 +89,18 @@ Each time an event handler is executed, a brand new `context` is created, and wi
|
||||
`context` is a brand new `:coeffect` map, which is initially totally empty.
|
||||
|
||||
That pristine `context` value (containing a pristine `:coeffect` map) is threaded
|
||||
through a chain of Interceptors before it is finally handled to our event handler,
|
||||
through a chain of Interceptors before it finally reaches our event handler,
|
||||
sitting on the end of a chain, itself wrapped up in an interceptor. We know
|
||||
this story well from a previous tutorial.
|
||||
|
||||
So, all members of the Interceptor chain have the opportunity to add to `:coeffects`
|
||||
via their `:before` function. This is where `:coeffect` magic happens. This is how
|
||||
new keys can be added to `:coeffect`, so that later our handler magically finds the
|
||||
new keys can be added to `:coeffect`, so that later our event handler magically finds the
|
||||
right data (like `:local-store`) in its `cofx` argument. It is the Interceptors.
|
||||
|
||||
### Which Interceptors?
|
||||
|
||||
If Interceptors put data in `:coeffect`, then we'll need to add the right ones to
|
||||
If Interceptors put data in `:coeffect`, then we'll need to add the right ones
|
||||
when we register our event handler.
|
||||
|
||||
Something like this (this handler is the same as before, except for one detail):
|
||||
@ -113,9 +114,9 @@ Something like this (this handler is the same as before, except for one detail):
|
||||
{:db (assoc db :defaults val))}))
|
||||
```
|
||||
|
||||
So we've added one Interceptor. It will inject the right key/value pair
|
||||
into `context's` `:coeffeects` which then goes on to be the first argument
|
||||
to our event handler (`cofx`)
|
||||
Look at that - my event handler has a new Interceptor! It is injecting the right key/value pair (`:local-store`)
|
||||
into `context's` `:coeffeects`, which then goes on to be the first argument
|
||||
to our event handler (`cofx`).
|
||||
|
||||
|
||||
### `inject-cofx`
|
||||
@ -148,7 +149,7 @@ I could create an event handler which has access to 3 coeffects:
|
||||
... in here I can access cofx's keys :now :local-store and :random-int))
|
||||
```
|
||||
|
||||
But that's probably just showing off, and not very useful.
|
||||
But that's probably just greedy, and not very useful.
|
||||
|
||||
And so, to the final piece in the puzzle: how does `inject-cofx`
|
||||
know what to do when it is given `:now` or `:local-store`?
|
||||
@ -162,13 +163,13 @@ It allows you associate a`cofx-id` (like `:now` or `:local-store`) with a
|
||||
handler function that injects the right key/value pair.
|
||||
|
||||
The function you register will be passed two arguments:
|
||||
- a `:coeffects` map (to which it should add key/value pair), and
|
||||
- a `:coeffects` map (to which it should add a key/value pair), and
|
||||
- optionally, the additional value supplied to `inject-cofx`
|
||||
and it is expected to return a modified `:coeffects` map.
|
||||
|
||||
### Examples Of `reg-cofx`
|
||||
### Example Of `reg-cofx`
|
||||
|
||||
Above we wrote an event handler that wanted `:now` data to be available. Here
|
||||
Above, we wrote an event handler that wanted `:now` data to be available. Here
|
||||
is how a handler could be registered for `:now`:
|
||||
```clj
|
||||
(reg-cofx ;; registration function
|
||||
@ -177,7 +178,17 @@ is how a handler could be registered for `:now`:
|
||||
(assoc coeffects :now (js.Date.)))) ;; add :now key, with value
|
||||
```
|
||||
|
||||
And there's this example:
|
||||
The outcome is:
|
||||
1. because that cofx handler above is now registered for `:now`, I can
|
||||
2. add an Interceptor to an event handler which
|
||||
3. looks like `(inject-cofx :now)`
|
||||
4. which means within that event handler I can access a `:now` value from `cofx`
|
||||
|
||||
As a result, my event handler is pure.
|
||||
|
||||
### Another Example Of `reg-cofx`
|
||||
|
||||
This:
|
||||
```clj
|
||||
(reg-cofx ;; new registration function
|
||||
:local-store
|
||||
@ -188,38 +199,62 @@ And there's this example:
|
||||
```
|
||||
|
||||
|
||||
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.
|
||||
With these two registrations in place, I could now use both `(inject-cofx :now)` and
|
||||
`(inject-cofx :local-store "blah")` in an event handler's interceptor chain.
|
||||
|
||||
To put this another way: I can't use `(inject-cofx :blah)` UNLESS I have previously
|
||||
used `reg-cofx` to register a handler for `:blah`. Otherwise `inject-cofx` doesn't
|
||||
know how to inject a `:blah`.
|
||||
|
||||
### Secret Interceptors
|
||||
|
||||
In a previous tutorial we learned that `reg-events-db`
|
||||
and `reg-events-fx` add Interceptors to front of any chain
|
||||
during registration.We found they inserted an Interceptor called `do-fx`.
|
||||
during registration. We found they inserted an Interceptor called `do-fx`.
|
||||
|
||||
I can now reveal that
|
||||
they also add `(inject-cofx :db)` at the front of each chain.
|
||||
|
||||
Guess what that injects into the `:coeffects` of every event handler?
|
||||
Guess what that injects into the `:coeffects` of every event handler? This is how `:db`
|
||||
is always available to event handlers.
|
||||
|
||||
Okay, so that was the last surprise. Now you know everything.
|
||||
|
||||
If ever you wanted to use DataScript, instead of an atom-containing-a-map
|
||||
like `app-db`, you'd replace `reg-event-db` and `reg-event-fx` with your own
|
||||
registration functions and have them auto insert the DataScript connection.
|
||||
|
||||
|
||||
### Testing
|
||||
|
||||
During testing, you may want to mock certain coeffets.
|
||||
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`, not a true random number.
|
||||
using a specific `now`, not a true random number.
|
||||
|
||||
In your test, you'd mock out the cofx handler:
|
||||
```
|
||||
(reg-cofx
|
||||
:random-int
|
||||
:now
|
||||
(fn [coeffects _]
|
||||
(assoc coeffects :random-int 5))) ;; 5 is not very random
|
||||
(assoc coeffects :now (js/Date. 2016 1 1))) ;; now was then
|
||||
```
|
||||
|
||||
If your test does alter registered coeffect handlers, and you are using `cljs.test`,
|
||||
then you can use a `fixture` to restore all coeffects at the end of your test:
|
||||
```clj
|
||||
(defn re-frame-fixture
|
||||
[f]
|
||||
(let [restore-re-frame-fn (re-frame.core/make-restore-fn)]
|
||||
(try
|
||||
(f)
|
||||
(finally (restore-re-frame-fn)))))
|
||||
|
||||
(cljs.test/use-fixtures :each re-frame-fixture)
|
||||
```
|
||||
|
||||
`re-frame.core/make-restore-fn` creates a checkpoint for re-frame state (including
|
||||
registered handlers) to which you can return.
|
||||
|
||||
### The 5 Point Summary
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user