Skip to content

Commit 5f5fd0d

Browse files
committed
[docs] Polish & update
1 parent 3a1b703 commit 5f5fd0d

File tree

4 files changed

+131
-90
lines changed

4 files changed

+131
-90
lines changed

docs/Flows.md

+53-57
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ More concretely, when the values change at one or more paths within `app-db`,
1919
then the value at another path is "automatically" recalculated.
2020

2121
## Why do we need flows?
22-
We turn to flows when we need a dynamic relationship between values - a "difference which makes a difference" ([Bateson](http://faculty.washington.edu/jernel/521/Form.htm)).
22+
We turn to flows when we need a dynamic relationship between values - a ["difference which makes a difference"](http://faculty.washington.edu/jernel/521/Form.htm).
2323

2424
For instance, how would you model this problem?
2525

@@ -38,7 +38,7 @@ We think flows offer a [Better Way](/re-frame/flows-advanced-topics#a-better-way
3838
!!! Note "The DataFlow Paradigm"
3939
Dataflow programming emerged in the 1970s, so it is almost as foundational as functional programming.
4040
Indeed, reactive programming - so much the rage these days - is simply a subset of dataflow programming.
41-
In contrast with imperative building blocks like 'if/then', 'next' and 'goto',
41+
In contrast with imperative building blocks like `if/then`, `next` and `goto`,
4242
dataflow programming implements control flow via the propagation of change.
4343
Both the functional and dataflow paradigms have profoundly influenced the design of re-frame.
4444
Hence, `re-frame's` tagline: "derived data, flowing".
@@ -242,11 +242,12 @@ Many such tasks amount to synchronization - maintaining an invariant within a ch
242242
And of course, a task which seems complex may just be a chain of simple tasks.
243243

244244
One relatable example is that of trying to maintain cascading error states. Imagine your UI has a validation rule: `start date` must be before `end date`.
245-
After the user changes either value, the error state must be calculated. This is used to determine if the `submit` button is enabled or not, and if an error message is displayed or not.
245+
After the user changes either value, the error state must be calculated.
246+
The result indicates whether to enable the submit button or display an error message.
246247

247-
Now, imagine your UI has many validation rules, and an error state must be calculated for each of them.
248-
In this case, the submit button state is a secondary calculation which combines these error states.
249-
Cascading, derived values.
248+
Now, imagine your UI has many validation rules, each with its own error state.
249+
In this case, the submit button state is a secondary calculation which combines these error states.
250+
Cascading, derived values.
250251

251252
Data flows from the leaves (what the user entered), through intermediate nodes (error predicate functions), through to the root (submit button state).
252253
Both the intermediate values and the root value are important.
@@ -280,6 +281,7 @@ Just like with layered subscriptions, one flow can use the value of another. Rem
280281
</div>
281282

282283
## Subscribing to flows
284+
283285
In our examples so far, we've used a regular subscription, getting our flow's output path.
284286
In `re-frame.alpha`, you can also subscribe to a flow by name.
285287
This bypasses the [caching behavior](/re-frame/flows-advanced-topics#caching) of a standard subscription.
@@ -335,21 +337,7 @@ For this, we use a `:live?` function.
335337
The quote above deals with phenomenal life, but you can also think of `:live?` as in a tv or internet broadcast.
336338
Data flows, but only when the flow itself is live.
337339

338-
Here's another room area flow:
339-
340-
<div class="cm-doc" data-cm-doc-result-format="pass-fail">
341-
(rf/reg-flow
342-
{:id :kitchen-area
343-
:inputs {:w [:kitchen :width]
344-
:h [:kitchen :length]}
345-
:output (fn [{:keys [w h]}] (* w h))
346-
:path [:kitchen :area]
347-
:live-inputs {:tab [:tab]}
348-
:live? (fn [{:keys [tab]}]
349-
(= tab :kitchen))})
350-
</div>
351-
352-
A barebones tab picker, and something to show us the value of `app-db`:
340+
Let's try it out. For example, here's a barebones tab picker, and something to show us the value of `app-db`:
353341

354342
<div class="cm-doc">
355343
(def tabs [:kitchen :garage])
@@ -382,15 +370,25 @@ A barebones tab picker, and something to show us the value of `app-db`:
382370

383371
### Live?
384372

385-
Here's a more advanced version of our kitchen calculator flow.
386-
This replaces our first `:kitchen-area` flow, since it has the same `:id`:
387-
373+
Here's a more advanced version of our room calculator flow.
388374

389-
Notice the different types of inputs. `:w [:kitchen :width]` represents an input as an `app-db` path, while `:tab :current-tab` identifies the value from the `:current-tab` flow we defined earlier.
375+
<div class="cm-doc" data-cm-doc-result-format="pass-fail">
376+
(rf/reg-flow
377+
{:id :kitchen-area
378+
:inputs {:w [:kitchen :width]
379+
:h [:kitchen :length]}
380+
:output (fn [{:keys [w h]}] (* w h))
381+
:path [:kitchen :area]
382+
:live-inputs {:tab [:tab]}
383+
:live? (fn [{:keys [tab]}]
384+
(= tab :kitchen))})
385+
</div>
390386

391-
Also, notice the new `:tab` input, and the new `:live?`.
387+
Notice the new `:live-inputs` and `:live?` keys.
388+
Just like `:output`, `:live:?` is a function of the resolved `:live-inputs`.
392389

393-
Just like `:output`, `:live:?` is a function of `app-db` and the `:inputs`. Re-frame only calculates the `:output` when the `:live?` function returns a truthy value. Otherwise, the flow is presumed dead.
390+
Re-frame only calculates the `:output` when the `:live?` function returns a truthy value.
391+
Otherwise, the flow is presumed dead.
394392

395393
Let's test it out:
396394

@@ -410,7 +408,8 @@ Let's test it out:
410408

411409
<div id="tabbed-app"></div>
412410

413-
Try switching tabs. Notice how `:area` only exists when you're in the `room-calculator` tab. What's happening here?
411+
Try switching tabs.
412+
Notice how the path `[:kitchen :area]` only exists when you're in the `room-calculator` tab. What's happening here?
414413

415414
### Lifecycle
416415

@@ -419,14 +418,12 @@ Depending on the return value of `:live?`, re-frame handles one of 4 possible st
419418

420419
| transition | action |
421420
|---|---|
422-
| From **live** to **live** | run `:output` |
423-
| From **dead** to **live** | run `:init` and `:output` |
421+
| From **live** to **live** | run `:output` (when `:inputs` have changed) |
422+
| From **dead** to **live** | run `:output` |
424423
| From **live** to **dead** | run `:cleanup` |
425424
| From **dead** to **dead** | do nothing |
426425

427-
Basically, *living* flows get output, *dying* flows get cleaned up, *arising* flows get initiated and output.
428-
429-
And independently of all this, `:output` only runs when `:inputs` have changed value.
426+
Basically, *arising* flows get output, *living* flows get output as needed, and *dying* flows get cleaned up.
430427

431428
### Cleanup
432429

@@ -447,42 +444,42 @@ The point is, *you* express when the signal lives or dies, not your render tree.
447444

448445
## Redefining and Undefining
449446

450-
Not only do flows have a lifecycle (defined by `:live?`, `:init` and `:cleanup`), but this lifecycle also includes registration and deregistration.
447+
Not only do flows have a lifecycle (defined by `:live?` and `:cleanup`), but this lifecycle also includes registration and deregistration.
451448

452449
- When you call `reg-flow`, that flow comes alive.
453-
- `:init` and `:output` run, even if the inputs haven't changed.
450+
- `:output` runs, even if the inputs haven't changed.
454451
- That's because the flow itself has changed.
455452
- When you call `clear-flow`, it dies (running `:cleanup`).
456453
- Re-frame provides `:reg-flow` and `:clear-flow` [effects](#re-frame/Effects/) for this purpose.
457454

458455
Here's another demonstration. Think of it as a stripped-down todomvc.
459456
You can add and remove items in a list:
457+
460458
<div class="cm-doc">
461-
(rf/reg-sub ::items :-> (comp reverse ::items))
459+
(rf/reg-sub :items :-> (comp reverse :items))
462460

463461
(rf/reg-event-db
464462
::add-item
465-
(fn [db [_ id]] (update db ::items conj id)))
463+
(fn [db [_ id]] (update db :items conj id)))
466464

467465
(rf/reg-event-db
468466
::delete-item
469-
(fn [db [_ id]] (update db ::items #(remove #{id} %))))
467+
(fn [db [_ id]] (update db :items #(remove #{id} %))))
470468

471469
(defn item [id] [:div "Item" id])
472470

473471
(defn items []
474-
(into [:div] (map item) @(rf/subscribe [::items])))
472+
(into [:div] (map item) @(rf/subscribe [:items])))
475473

476474
(defn controls []
477-
(let [id (atom 0)]
478-
(fn []
479-
[:div
480-
[:span {:style clickable
481-
:on-click #(do (rf/dispatch [::add-item (inc @id)])
482-
(swap! id inc))} "Add"] " "
483-
[:span {:style clickable
484-
:on-click #(do (rf/dispatch [::delete-item @id])
485-
(swap! id dec))} "Delete"] " "])))
475+
(let [id (or (apply max @(rf/subscribe [:items])) 0)]
476+
[:div
477+
[:span {:style clickable
478+
:on-click #(rf/dispatch [::add-item (inc id)])}
479+
"Add"] " "
480+
[:span {:style clickable
481+
:on-click #(rf/dispatch [::delete-item id])}
482+
"Delete"] " "]))
486483

487484
(defonce item-counter-basic-root
488485
(rdc/create-root (js/document.getElementById "item-counter-basic")))
@@ -516,10 +513,9 @@ It builds a flow that validates our item list against the requirements:
516513

517514
<div class="cm-doc" data-cm-doc-result-format="pass-fail">
518515
(defn error-state-flow [{:keys [min-items max-items] :as requirements}]
519-
{:id ::error-state
520-
:path [::error-state]
521-
:inputs {:items [::items]
522-
:tab (rf/flow<- :current-tab)}
516+
{:id :error-state
517+
:path [:error-state]
518+
:inputs {:items [:items]}
523519
:output (fn [{:keys [items]}]
524520
(let [ct (count items)]
525521
(cond
@@ -535,15 +531,15 @@ And register a flow that fits our base requirements:
535531
</div>
536532

537533
Now this flow is calculating an error-state value, and adding it to `app-db` after every event.
538-
This happens as long as the `::items` have changed... right?
539-
Actually, there's another way to make a flow recalculate - we can reregister it.
534+
This happens as long as the `:items` have changed... right?
535+
Actually, there's another way to make a flow recalculate - we can re-register it.
540536

541537
Let's update the app to display our new error state:
542538

543539
<div class="cm-doc">
544540

545541
(defn warning []
546-
(let [error-state (rf/sub :flow {:id ::error-state})]
542+
(let [error-state (rf/sub :flow {:id :error-state})]
547543
[:div {:style {:color "red"}}
548544
(->> @error-state
549545
(get {:too-many "Too many items. Please remove one."
@@ -594,9 +590,9 @@ And a corresponding event, which triggers our `:reg-flow` effect:
594590
What happens after `:reg-flow` runs? Are there now two flows? Actually, no.
595591

596592
- If you register a new flow with the same `:id`, it replaces the old one.
597-
- When we trigger `[:reg-flow (error-state-flow ...)]`
593+
- When we trigger `[:reg-flow (error-state-flow ...)]`:
598594
- The old `:error-state` flow runs `:cleanup`
599-
- The new `:error-state` flow runs `:init` and `:output`
595+
- The new `:error-state` flow runs `:output`
600596

601597
Not only does changing the inputs lead to new output, but so does changing the flow itself.
602598
Let's test it out:

0 commit comments

Comments
 (0)