This commit is contained in:
Mike Thompson 2016-12-04 07:57:31 +11:00
parent 31ce3903bf
commit 765b116ae6
7 changed files with 181 additions and 176 deletions

View File

@ -2,17 +2,13 @@
Let's understand how re-frame manages application state.
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
#### Table Of Contents
- [On Data](#on-data)
- [The Big Ratom](#the-big-ratom)
- [The Benefits Of Data-In-The-One-Place](#the-benefits-of-data-in-the-one-place)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
### On Data
<blockquote class="twitter-tweet" lang="en"><p>Well-formed Data at rest is as close to perfection in programming as it gets. All the crap that had to happen to put it there however...</p>&mdash; Fogus (@fogus) <a href="https://twitter.com/fogus/status/454582953067438080">April 11, 2014</a></blockquote>
@ -36,7 +32,7 @@ spent your life breaking systems into pieces, organised around behaviour and try
to hide state. I still wake up in a sweat some nights thinking about all
that Clojure data lying around exposed and passive.
But, as Fogus reminded us, data at rest is quite perfect.
But, as Fogus reminds us, data at rest is quite perfect.
In re-frame's reference implementation, `app-db` is one of these:
```clj
@ -62,7 +58,7 @@ Further Notes:
be a [datascript](https://github.com/tonsky/datascript database). In fact, any database which
can signal you when it changes would do. We'd love! to be using [datascript](https://github.com/tonsky/datascript database) - so damn cool -
but we had too much data in our apps. If you were to use it, you'd have to tweak the
reference implementation a bit, [perhaps using this inspiration](https://gist.github.com/allgress/11348685).
reference implementation a bit and use [Posh](https://github.com/mpdairy/posh).
### The Benefits Of Data-In-The-One-Place

View File

@ -1,39 +1,15 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
- [Initial Code Walk-through](#initial-code-walk-through)
- [What Code?](#what-code)
- [What Does It Do?](#what-does-it-do)
- [Namespace](#namespace)
- [Data Schema](#data-schema)
- [Events (domino 1)](#events-domino-1)
- [dispatch](#dispatch)
- [After dispatch](#after-dispatch)
- [Event Handlers (domino 2)](#event-handlers-domino-2)
- [reg-event-db](#reg-event-db)
- [:initialize](#initialize)
- [:timer](#timer)
- [:time-color-change](#time-color-change)
- [Effect Handlers (domino 3)](#effect-handlers-domino-3)
- [Subscription Handlers (domino 4)](#subscription-handlers-domino-4)
- [View Functions (domino 5)](#view-functions-domino-5)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Initial Code Walk-through
At this point in your reading, you are armed with:
- a high level understanding of the 6 domino process (from re-frame's README)
- an understanding of application state (from the previous tutorial)
In this tutorial, **we'll look at re-frame code**. Finally.
### What Code?
This repo contains an `/example` application called "simple",
which has around 70 lines of code. We'll look at every line
and understand what it does.
which has around 70 lines of code. We'll look at every line.
You are currently about 50% the way to understanding re-frame. By the
end of this tutorial, you'll be at 70%, which is good
@ -70,15 +46,15 @@ for your application state (the data stored in `app-db`). But,
here, to minimise cognitive load, we'll cut that corner.
But ... we can't cut it completely. You'll still need an
informal description ... for this app `app-db` will contain
informal description, and here it is ... for this app `app-db` will contain
a two-key map like this:
```cljs
{:time (js/Date.) ;; current time for display
:time-color "#f88"} ;; what colour should the time be shown in
{:time (js/Date.) ;; current time for display
:time-color "#f88"} ;; the colour in which the time should be be shown
```
re-frame itself owns/manages `app-db` (see FAQ #1),
supplying the value within it (a two-key map in this case)
re-frame itself owns/manages `app-db` (see FAQ #1), and it will
supply the value within it (a two-key map in this case)
to your various handlers as required.
## Events (domino 1)
@ -145,7 +121,7 @@ The `router`:
for this kind of event
3. calls that event handler with the necessary arguments
As a re-frame app developer, your job then is to write and register a handler
As a re-frame app developer, your job, then, is to write and register a handler
for each kind of event.
## Event Handlers (domino 2)
@ -173,7 +149,7 @@ The handler function you provide should expect two parameters:
- `db` the current application state
- `v` the event vector
So your function will have a signature like this: `(fn [db v] ...)`.
So, your function will have a signature like this: `(fn [db v] ...)`.
Each event handler must compute and return the new state of
the application, which means it normally returns a
@ -192,14 +168,15 @@ to be computed. More on this soon.
### :initialize
On startup, application state must be initialised. We
want to put a sensible value into `app-db`.
want to put a sensible value into `app-db` which will
otherwise contain `{}`.
So a `(dispatch [:initialize])` will happen early in the
apps life (more on this below), and we need to write an `event handler`
for it.
Now this event handler is slightly unusual because it doesn't
much care what the value in `db` - it just wants to plonk
much care about the existing value in `db` - it just wants to plonk
in a new complete value.
Like this:
@ -216,7 +193,7 @@ This particular handler `fn` ignores the two parameters
a map literal, which becomes the application
state.
Here's an alternative way of writing it:
Here's an alternative way of writing it which does pay attention to the existing value of `db`:
```clj
(rf/reg-event-db
:initialize
@ -226,8 +203,6 @@ Here's an alternative way of writing it:
(assoc :time-color "#f88")))
```
`app-db` starts off holding a `{}` value. So we assume `db` will be `{}`
but, irrespective, we just assoc into it.
### :timer
@ -286,27 +261,25 @@ source data from other subscriptions. So a tree of dependencies
results.
The Views (Domino 5) are the leaves. The root is `app-db` and the
intermediate nodes are computations being performed by
Each query is identified by an `id` XXXX
intermediate nodes are computations being performed by Domino 4 query functions.
Now, the two examples below are utterly trivial. They just extract part of the application
state and return it. So, there's virtually no computation. More interesting
state and return it. So, there's virtually no computation. More interesting, layered
subscriptions and more explanation can be found in the todomvc example.
`reg-sub` associates a `query id` with a function which computes
that query. It's use looks like this:
```clj
(reg-sub
:some-query-id ;; query identifier
some-function) ;; the function which will compute the query
:some-query-id ;; query id (used later in subscribe)
a-query-fn) ;; the function which will compute the query
```
If, later, we see a view function requesting data like this:
`(listen [:some-query-id])` ;; note use of `:some-query-id` XXX using listen
then `some-function` will be used to perform the query over application state.
`(subscribe [:some-query-id])` ;; note use of `:some-query-id`
then `a-query-fn` will be used to perform the query over application state.
Each time application state changes, `some-function` will be
called again to compute a new materialised view (a new computation)
Each time application state changes, `a-query-fn` will be
called again to compute a new materialised view (a new computation over app state)
and that new value will be given to any view function which is subscribed
to `:some-query-id`. The view function itself will then also be called again
to compute new DOM (because it depends on a query value which changed).
@ -328,7 +301,7 @@ Here's the code:
(:time-color db)))
```
Like I said, both of these queries are trivial.
Like I said, both of these queries are trivial. See todomvc for more interesting ones.
## View Functions (domino 5)
@ -358,13 +331,17 @@ And if we call it:
Yep, that's a vector with two elements: a keyword and a string.
Now, greet is pretty simple and there's no "Data In", here, just "Hiccup out".
Now,`greet` is pretty simple and there's no "Data In", here, just "Hiccup out".
### Sourcing data.
### Sourcing data
In order to render a DOM representation of the application state, view functions
must first obtain that state. This happens via subscriptions.
> XXX This particular document is a WIP ... it peters out after this ... I wouldn't read any more.
-----
transform data into data. They source
data from subscriptions (queries across application state), and
the data they return is hiccup-formatted, which is a proxy for DOM.
@ -459,5 +436,4 @@ the synchronous
using standard, supplied ones.
- write and register query functions which implement nodes in a signal graph (query layer) (domino 4)
- write Reagent view functions (view layer) (domino 5)
re-frame's job is to XXX

View File

@ -32,7 +32,7 @@ and get you reading and writing code ASAP.
**But** there are other interesting perspectives on re-frame
which will deepen your understanding of its design,
and enable you to get the best from it.
and help you to get the best from it.
This tutorial is a tour
of these ideas, justifications and insights. It is a little rambling, but I
@ -48,7 +48,8 @@ For all its considerable brilliance, Reagent (+ React)
delivers only the 'V' part of a traditional MVC framework.
But apps involve much more than V. We build quite complicated
SPAs which can run to 50K lines of code. So, where does the control logic go? How is state stored & manipulated? etc.
SPAs which can run to 50K lines of code. So, I wanted to know:
where does the control logic go? How is state stored & manipulated? etc.
We read up on [Pedestal App], [Flux],
[Hoplon], [Om], early [Elm], etc and re-frame is the architecture that
@ -78,8 +79,7 @@ __Second__, we believe in ClojureScript, immutable data and the process of build
a system out of pure functions.
__Third__, we believe in the primacy of data, for the reasons described in
the main README. re-frame has a data oriented functional architecture. It
implements an infinite loop of Derived data.
the main README. re-frame has a data oriented, functional architecture.
__Fourth__, we believe that Reactive Programming is one honking good idea.
How did we ever live without it? It is a quite beautiful solution to one half of re-frame's
@ -122,7 +122,7 @@ The only way the app "moves forwards" is via events. "Replaying events" moves yo
step by step towards the error causing problem.
This is perfect for debugging assuming, of course, you are in a position to capture
an app state checkpoint, and the events since then.
a checkpoint of `app-db`, and the events since then.
Here's Martin Fowler's [description of Event Sourcing](http://martinfowler.com/eaaDev/EventSourcing.html).
@ -167,8 +167,8 @@ allows:
2. Event handlers sometimes need coeffects (arguments) in addition to `db` and `v`.
But, even if it isn't the full picture, it is a very useful
and interesting mental model. We first saw it in Elm's early use
of `foldp` (fold from the past), which was later enshrined in the
and interesting mental model. We were first exposed to this idea
via Elm's early use of `foldp` (fold from the past), which was later enshrined in the
Elm Architecture.
And for the love of all that is good, please watch this terrific
@ -178,21 +178,21 @@ all the problems that evaporate.
Think about that: shared mutable state (the root of all evil),
re-imagined as a stream!! Blew my socks off.
If, by chance, you have watched that video, you might twig to
the idea that `app-db` is really a derived value .. the video talks
If, by chance, you ever watched that video (you should!), you might then twig to
the idea that `app-db` is really a derived value ... the video talks
a lot about derived values. So, yes, app-db is a derived value of the `perpetual reduce`.
And yet, it acts as the authoritative source of state in the app. And yet,
it isn't, it is simply a piece of derived state. And yet, it is the source.
it isn't, it is simply a piece of derived state. And yet, it is the source. Etc.
This is an infinite loop of sorts. An infinite loop of derived data.
This is an infinite loop of sorts - an infinite loop of derived data.
## It does FSM
> Any sufficiently complicated GUI contains an ad hoc,
> informally-specified, bug-ridden, slow implementation
> of a hierarchical Finite State Machine <br>
> -- [my 11th rule](https://en.wikipedia.org/wiki/Greenspun%27s_tenth_rule)
> -- [me failing to impress my two twitter followers]
`event handlers` collectively
implement the "control" part of an application. Their logic
@ -233,13 +233,14 @@ Events - that's the way we roll.
My job is to be a relentless cheerleader for re-frame, right?
The gyrations of my Pom-Poms should be tectonic,
but the following quote just makes me smile. It should
but the following quote makes me smile. It should
be taught in all ComSci courses.
> We begin in admiration and end by organizing our disappointment <br>
> &nbsp;&nbsp;&nbsp; -- Gaston Bachelard (French philosopher)
Of course, that only applies if you get passionate about a technology.
Of course, that only applies if you get passionate about a technology
(a flaw of mine).
But, no. No! Those French Philosophers and their pessimism - ignore him!!
Your love for re-frame will be deep, abiding and enriching.
@ -259,63 +260,15 @@ Using data gives us:
- logability and event sourcing
- a more flexible version of "partial" (curring)
## Derived Data
**Derived data is flowing around the
loop, reactively, through pure functions.** There is a pause in the loop whenever we wait
for a new event, but the moment we get it, it's another iteration of the "derived data" FRP loop.
Derived values, all the way down, forever.
## Prefer Dumb Views - Part 1
Many events are dispatched by the DOM in response to user actions.
For example, a button view might be like this:
```clj
(defn yes-button
[]
[:div {:class "button-class"
:on-click #(dispatch [:yes-button-clicked])}
"Yes"])
```
Notice that `on-click` DOM handler:
```clj
#(dispatch [:yes-button-clicked])
```
With re-frame, we want the DOM as passive as possible. We do
not want our views containing any imperative control logic.
All of that should be computed by event handlers.
We want that "on-click" as simple as we can make it.
**Rule**: `views` are as passive and minimal as possible when it
comes to handling events. They `dispatch` pure data and nothing more.
## Prefer Dumb Views - Part 2
Neither do we want views computing the data they render.
That's the job of a subscription:
So this is bad:
```clj
(defn show-items
[]
(let [sorted-items (sort @(subscribe [:items]))] ;; <--
(into [:div] (for [i sorted-items] [item-view i]))))
```
The view is not simply taking the data supplied by the
## Full Stack
If you like re-frame and want to take the principles full-stack, then
these resource might be interesting:
Commander Pattern
Commander Pattern
https://www.youtube.com/watch?v=B1-gS0oEtYc
Datalog All The Way Down
https://www.youtube.com/watch?v=aI0zVzzoK_E

View File

@ -58,7 +58,7 @@ Architecturally, re-frame implements "a perpetual loop".
To build an app, you hang pure functions on certain parts of this loop,
and re-frame looks after the `conveyance of data`
around the loop, into and out of the transforming functions you
provide - hence the tag line "Derived Data, Flowing".
provide - which is why the tag line is "Derived Data, Flowing".
### It does Physics
@ -70,7 +70,7 @@ Two distinct stages, involving water in different phases, being acted upon
by different forces: gravity working one way, evaporation/convection the other.
To understand re-frame, **imagine data flowing around that loop instead of water**. re-frame
provides the "conveyance" of the data - the equivalent of gravity, evaporation and convection.
provides the conveyance of the data - the equivalent of gravity, evaporation and convection.
You design what's flowing and then you hang functions off the loop at
various points to compute the data's phase changes.
@ -86,12 +86,14 @@ you to understand re-frame, is **practically proof** it does physics.
<img align="right" src="/images/Readme/Dominoes-small.jpg?raw=true">
Computationally, each iteration of the loop involves a
6 domino cascade.
6 domino cascade.
One domino triggers the next, which triggers the next, etc,
until we are back at the beginning of the loop. Each iteration is the same cascade.
### 1st Domino
Here are the 6 dominoes ...
### 1st Domino
An `event` is sent when something happens - the user
clicks a button, or a websocket receives a new message.
@ -107,11 +109,11 @@ re-frame is `event` driven.
In response to an `event`, an application must compute
the implication (the ambition, the intent). This is known as `event handling`.
Event handler functions compute `effects`. Or, more accurately, they compute
a **description of `effects`**, which means they say, declaratively,
how the world should change (because of the event).
Event handler functions compute `effects` or, more accurately,
a **description of `effects`**. So they compute a data structure
which says, declaratively, how the world should change (because of the event).
Much of the time, only the state of the SPA itself need
Much of the time, only the "app state" of the SPA itself need
change, but sometimes the outside world must too must be effected
(localstore, cookies, databases, emails, logs, etc).
@ -131,26 +133,27 @@ it does so in a controlled, debuggable, auditable, mockable, plugable way.
### Then what happens?
So, that 3rd domino just changed the world and, very often, that involves
So, that 3rd domino just changed the world and, very often,
one particular part of the world, namely the **app's state**.
re-frame `app state` is held in one place - think of it like you
re-frame's `app state` is held in one place - think of it like you
would an in-memory, central database for the app.
When domino 3 changes `app state`, it triggers the next part of the cascade
When domino 3 changes this `app state`, it triggers the next part of the cascade
involving dominoes 4-5-6.
### The view formula
The 4-5-6 domino cascade implements the formula made famous by Facebook's ground-breaking React library:
`v = f(s)`
`v = f(s)`
A view `v` is a function `f` of the app state `s`.
Or, said another way, there are functions `f` which compute what DOM nodes, `v`,
should be displayed to the user when the application is in a given app state, `s`.
Or, another way: **over time**, as `s` changes, `f`
will be called each time to compute new `v`, forever keeping `v` up to date with the current `s`.
will be re-run each time to compute new `v`, forever keeping `v` up to date with the current `s`.
Now, in our case, it is domino 3 which changes `s`, the application state,
and, in response, dominoes 4-5-6 are about re-running `f` to compute the new `v`
@ -164,6 +167,9 @@ even be showing right now.
### Domino 4
Domino 4 is about extracting data from "app state". The right data,
in the right format for view functions (Domino 5).
Domino 4 is a novel and efficient de-duplicated signal graph which
runs query functions on the app state, `s`, efficiently computing
reactive, multi-layered, "materialised views" of `s`.
@ -196,7 +202,7 @@ browser DOM nodes are mutated.
can be be described, understood and
tested independently. They take data, transform it and return new data.
The loop itself is utterly predictable and very mechanical in operation.
The loop itself is very mechanical in operation.
So, there's a regularity, simplicity and
certainty to how a re-frame app goes about its business,
which leads, in turn, to an ease in reasoning and debugging.
@ -205,22 +211,21 @@ which leads, in turn, to an ease in reasoning and debugging.
The two sub-cascades 1-2-3 and 4-5-6 have a similar structure.
In each cascade, it is the 2nd to last domino which
computes "descriptions" of mutations required and it is
the last domino which actions these descriptions - it does the dirty work.
In each, it is the 2nd last domino which
computes "descriptions" of mutations required, and it is
the last domino which does rthe dirty work and realises these descriptions.
And in both case, you need worry yourself about this dirty work. re-frame looks
And in both case, you don't need to worry yourself about this dirty work. re-frame looks
after those dominoes.
## Code Fragments
Time to understand this
domino narrative in terms of code fragments.
Let's now understand this
domino narrative in terms of code fragments.
> You shouldn't expect
to fully grok all the code presented below. We're still in overview mode, getting
the 30,000 foot view, and
detail is missing. Plenty of tutorials and examples to follow.
to completely grok the code presented below. We're still in overview mode, getting
the 30,000 foot view. There are later tutorials for the details.
**Imagine:** the UI of an SPA shows a list of items. This user
clicks the "delete" button next to the 3rd item in a list.
@ -238,8 +243,8 @@ like this:
`dispatch` is the means by which you emit an `event`. An `event` is a vector and, in this case,
it has 2 elements: `[:delete-item 2486]`. The first element,
`:delete-item`, is the kind of event. The `rest` is optional, and is whatever else needs to
be known about the event - in this case, my made-up id, `2486`, for the item to delete.
`:delete-item`, is the kind of event. The `rest` is optional, further data about the `event`
- in this case, my made-up id, `2486`, for the item to delete.
### Code For Domino 2
@ -249,7 +254,7 @@ The `event handler`, `h`, associated with
This handler function, `h`, must take two arguments: the state-of-the-world
and the event, and it must return an effects map. Without going into any
explanations at this early point, here's a sketch of what a handler
might look like:
might look like:
```clj
(defn h
[{:keys [db]} event] ;; args: db from coeffect, event
@ -257,30 +262,36 @@ might look like:
{:db (dissoc-in db [:items item-id])})) ;; effect is change db
```
Sometime earlier, this event handler (function) `h` would have been associated with `:delete-item` in this way:
On program starup, this event handler (function) `h` would have been
associated with `:delete-item` in this way:
```clj
(re-frame.core/reg-event-fx :delete-item h)
```
### Code For Domino 3
An `effect handler` (function) actions the `effect` returned by the call to `h`.
That effect was the map:
An `effect handler` (function) actions the `effect` returned by the call to `h`.
That `effect` was the map:
```clj
{:db (dissoc-in db [:items item-id])}
```
Keys in this map identify the required effect, with the values supplying further details.
Keys in this map identify the required `effect`, with the values of the map
supplying further details.
A key of `:db` means to update the app state, with the value supplied.
A key of `:db` means to update the app state, with the new computed value.
This is a mutative
step, facilitated by re-frame, which you won't have to do explicitly.
The update of "app state", which re-frame manages for you,
is a mutative step, facilitated by re-frame itself
when it sees the `:db` effect.
Why the name `:db`? re-frame sees "app state" as something of an in-memory
database.
### Code For Domino 4
Because the app state changed, a query (function) over this app
state is called automatically (reactively), and it computes the list of items (which,
because of domino 3, has been updated to no longer contain the deleted item).
Because a new version of "app state" has been computed and installed,
a query (function) over this app state is called automatically (reactively),
itself computing the list of items.
Because the items
are stored in app state, there's not a lot to compute in this case. This
@ -291,14 +302,15 @@ subscription acts more like an accessor.
(:items db)) ;; not much of materialised view
```
Such a query-fn must be registered, (reasons become obvious in the next domino) like this:
On program startup, such a query-fn must be registered,
(for reasons obvious in the next domino) like this:
```clj
(re-frame.core/reg-sub :query-items query-fn)
```
### Code For Domino 5
Because the query function computed a new value, a view (function) which subscribes
Because the query function re-computed a new value, a view (function) which subscribes
to that value, is called automatically (reactively) to re-compute DOM. It produces
a hiccup-formatted data structure describing the DOM nodes required (no DOM nodes
for the deleted item, obviously, but otherwise the same DOM as last time).
@ -310,7 +322,7 @@ for the deleted item, obviously, but otherwise the same DOM as last time).
[div: (map item-render @items])) ;; assume item-render already written
```
Notice how `items` is "sourced". A view function uses `subscribe` with a key
Notice how `items` is "sourced" from "app state". View function use `subscribe` with a key
originally used to register a query function.
### Code For Domino 6
@ -321,16 +333,34 @@ The DOM "this
time" is the same as last time, except for the absence of DOM for the
deleted item.
The key point to understand about 3-4-5-6 is that a change to app state, triggers queries to rerun,
which, in turn, triggers views to rerun which, in turn, causes fresh DOM in the broiwser All reactively.
Boom, boom, boom.
One domino after the other. But with efficiency short circuits.
### 3-4-5-6 Summary
The key point to understand about our 3-4-5-6 example is:
- a change to app state ...
- triggers query functions to rerun ...
- which triggers view functions to rerun
- which causes new DOM
Boom, boom, boom go the dominoes.
It is a reactive data flow.
### Aaaaand we're done
At this point, the re-frame app returns to a quiescent state,
waiting for the next event.
## Your Job
When building a re-frame app, you will:
- design your app's information model (data and schema layer)
- write and register event handler functions (control and transition layer) (domino 2)
- (once in a blue moon) write and register effect and coeffect handler
functions (domino 3) which do the mutative dirty work of which we dare not
speak.
- write and register query functions which implement nodes in a signal graph (query layer) (domino 4)
- write Reagent view functions (view layer) (domino 5)
## It Leverages Data
You might already know that ClojureScript is a modern lisp, and that
@ -412,8 +442,7 @@ Client only: https://github.com/Day8/re-frame-template <br>
Front and back: http://www.luminusweb.net/
Use these resources: <br>
https://github.com/Day8/re-com
XXX
https://github.com/Day8/re-frame/blob/develop/docs/External-Resources.md
###

View File

@ -1,4 +1,10 @@
### [re-frame Introduction](../README.md)
### Introduction
This section contains a WIP rewrite of the existing README
into a number of smaller documents, with a different way of explaining re-frame.
- [re-frame's New README (soon)](NewREADME.md)
- [App State](ApplicationState.md)
- [Code Walk-Through](CodeWalkThrough.md)
- [Mental Model Omnibus](MentalModelOmnibus.md)
### Understanding Event Handlers

View File

@ -564,3 +564,48 @@ Back to the more pragmatic world ...
[Prismatic Schema]:https://github.com/Prismatic/schema
[Hoplon]:http://hoplon.io/
[Pedestal App]:https://github.com/pedestal/pedestal-app
-----------------
## Prefer Dumb Views - Part 1
Many events are dispatched by the DOM in response to user actions.
For example, a button view might be like this:
```clj
(defn yes-button
[]
[:div {:class "button-class"
:on-click #(dispatch [:yes-button-clicked])}
"Yes"])
```
Notice that `on-click` DOM handler:
```clj
#(dispatch [:yes-button-clicked])
```
With re-frame, we want the DOM as passive as possible. We do
not want our views containing any imperative control logic.
All of that should be computed by event handlers.
We want that "on-click" as simple as we can make it.
**Rule**: `views` are as passive and minimal as possible when it
comes to handling events. They `dispatch` pure data and nothing more.
## Prefer Dumb Views - Part 2
Neither do we want views computing the data they render.
That's the job of a subscription:
So this is bad:
```clj
(defn show-items
[]
(let [sorted-items (sort @(subscribe [:items]))] ;; <--
(into [:div] (for [i sorted-items] [item-view i]))))
```
The view is not simply taking the data supplied by the