Skip to content

Commit

Permalink
Add :fx effect handler
Browse files Browse the repository at this point in the history
See #639
  • Loading branch information
superstructor committed Aug 24, 2020
1 parent 937338c commit 44eb7d3
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 30 deletions.
33 changes: 9 additions & 24 deletions docs/Effects.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ When an event handler is registered via `reg-event-fx`, it must return effects.
:my-event
(fn [cofx [_ a]] ;; 1st argument is coeffects, instead of db
{:db (assoc (:db cofx) :flag a)
:dispatch [:do-something-else 3]})) ;; return effects
:fx [[:dispatch [:do-something-else 3]]]})) ;; return effects
```

`-fx` handlers return a description of the side-effects required, and that description is a map.
Expand All @@ -28,13 +28,14 @@ further data. The type of value depends on the specific side-effect.
Here's the two instructions from the example above:
```clj
{:db (assoc db :flag a) ;; side effect on app-db
:dispatch [:do-something-else 3]} ;; dispatch this event
:fx [[:dispatch [:do-something-else 3]]]} ;; dispatch this event
```

The `:db` key instructs that "app-db" should be `reset!` to the
value supplied.

And the `:dispatch` key instructs that an event should be
And the `:fx` key instructs that an ordered list of other effects should be
executed. In this case a `:dispatch` key instructs that an event should be
dispatched. The value is the vector to dispatch.

There are many other possible
Expand Down Expand Up @@ -182,29 +183,13 @@ of the interceptor chain. It is only a few lines of code.

## Order Of Effects?

There isn't one.
The `:db` side effect is guaranteed to be handled first.

`do-fx` does not currently provide you with control over the order in
which side effects occur. The `:db` side effect
might happen before `:dispatch`, or not. You can't rely on it.

!!! Note "Is That A Problem?"
If you feel you need ordering, then please
open an issue and explain the usecase. The current absence of
good usecases is the reason ordering isn't implemented. So give
us a usercase and we'll revisit, maybe.


!!! Note "For The Record"
If, later, ordering was needed, it might be handled via
metadata on `:effects`. Also, perhaps by allowing `reg-fx` to optionally
take two functions:

- an effects pre-process fn <-- new. Takes `:effects` returns `:effects`
- the effects handler (as already described above).

Anyway, these are all just possibilities. But not needed or implemented yet.
Effects in the collection of the `:fx` side effect are handled in the order
provided.

Effects other than `:db` in the top level map may occur in any order after `:db`,
so may occur before or after `:fx`.

## Effects With No Data

Expand Down
23 changes: 22 additions & 1 deletion docs/api-builtin-effects.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
</a>
</h3>
<ul>
<li class="depth-1">
<a href="api-builtin-effects.html#fx">
<div class="inner">
<span>:fx</span>
</div>
</a>
</li>
<li class="depth-1">
<a href="api-builtin-effects.html#dispatch-later">
<div class="inner">
Expand Down Expand Up @@ -51,7 +58,20 @@
In addition to the API provided by `re-frame.core`, re-frame provides a small number of
built-in effects which also contribute to the API.

## <a name="fx"></a> :fx

Handle one of more effects. Expects a collection of vectors (tuples) of the form

This comment has been minimized.

Copy link
@olivergeorge

olivergeorge Aug 24, 2020

Contributor

s/of/or/

`[effect-key effect-value]`. `nil` entries in the collection are ignored so
effects can be added conditionally.

usage:
```clojure
{:fx [[:dispatch [:event-id "param"]]
nil
[:http-xhrio {:method :post
...}]]}
```
## <a name="dispatch-later"></a> :dispatch-later

`dispatch` one or more events after given delays. Expects a collection
Expand Down Expand Up @@ -119,7 +139,8 @@ or:

## <a name="db"></a> :db

reset! app-db with a new value. `value` is expected to be a map.
reset! app-db with a new value. `value` is expected to be a map. This is always
executed first before any other effect.

usage:
```clojure
Expand Down
11 changes: 11 additions & 0 deletions docs/releases/2020.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@
## Unreleased

#### Added

- Add `:fx` effect handler. See [#639](https://github.com/day8/re-frame/issues/639).

#### Changed

- The `:db` effect is now guaranteed to be handled first. This is in support of the introduction of
the new `:fx` effect handler. See [#639](https://github.com/day8/re-frame/issues/639).
- An unknown key in the effect map returned from a `reg-event-fx` is now a warning, not an error.
See [#639](https://github.com/day8/re-frame/issues/639).

## 1.0.0 (2020-07-20)

No changes since 1.0.0-rc6. Final 1.0.0 release.
Expand Down
41 changes: 36 additions & 5 deletions src/re_frame/fx.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,23 @@
value for that key - so in the example above the effect handler for :dispatch
will be given one arg `[:hello 42]`.
You cannot rely on the ordering in which effects are executed."
You cannot rely on the ordering in which effects are executed, other than that
`:db` is guaranteed to be executed first."
(->interceptor
:id :do-fx
:after (fn do-fx-after
[context]
(trace/with-trace
{:op-type :event/do-fx}
(doseq [[effect-key effect-value] (:effects context)]
(if-let [effect-fn (get-handler kind effect-key false)]
(effect-fn effect-value)
(console :error "re-frame: no handler registered for effect:" effect-key ". Ignoring.")))))))
(let [effects (:effects context)
effects-without-db (dissoc effects :db)]
;; :db effect is guaranteed to be handled before all other effects.
(when-let [new-db (:db effects)]
((get-handler kind :db false) new-db))
(doseq [[effect-key effect-value] effects-without-db]
(if-let [effect-fn (get-handler kind effect-key false)]
(effect-fn effect-value)
(console :warn "re-frame: no handler registered for effect:" effect-key ". Ignoring."))))))))

;; -- Builtin Effect Handlers ------------------------------------------------

Expand All @@ -78,6 +84,31 @@
(console :error "re-frame: ignoring bad :dispatch-later value:" effect)
(set-timeout! #(router/dispatch dispatch) ms)))))

;; :fx
;;
;; Handle one or more effects. Expects a collection of vectors (tuples) of the
;; form [effect-key effect-value]. `nil` entries in the collection are ignored
;; so effects can be added conditionally.
;;
;; usage:
;;
;; {:fx [[:dispatch [:event-id "param"]]
;; nil
;; [:http-xhrio {:method :post
;; ...}]]}
;;

(reg-fx
:fx
(fn [seq-of-effects]
(if-not (seq? seq-of-effects)
(console :error "re-frame: \":fx\" effect expects a seq, but was given " (type seq-of-effects))
(doseq [[effect-key effect-value] (remove nil? seq-of-effects)]
(when (= :db effect-key)
(console :warn "re-frame: \":fx\" effect should not contain a :db effect"))
(if-let [effect-fn (get-handler kind effect-key false)]
(effect-fn effect-value)
(console :warn "re-frame: in \":fx\" effect found " effect-key " which has no associated handler. Ignoring."))))))

;; :dispatch
;;
Expand Down

0 comments on commit 44eb7d3

Please sign in to comment.