Merge remote-tracking branch 'refs/remotes/Day8/master'
This commit is contained in:
commit
6bd664404f
|
@ -34,19 +34,19 @@ Assuming your larger apps has multiple "panels" (or "views") which are relativel
|
|||
|
||||
```
|
||||
src
|
||||
├── panel1
|
||||
├── panel-1
|
||||
│ ├── db.cljs <--- schema, validation, etc (data layer)
|
||||
│ ├── subs.cljs <--- subscription handlers (query layer)
|
||||
│ ├── views.cljs <--- reagent components (view layer)
|
||||
│ └── events.cljs <--- event handlers (control/update layer)
|
||||
├── panel2
|
||||
├── panel-2
|
||||
│ ├── db.cljs <--- schema, validation. etc (data layer)
|
||||
│ ├── subs.cljs <--- subscription handlers (query layer)
|
||||
│ ├── views.cljs <--- reagent components (view layer)
|
||||
│ └── events.cljs <--- event handlers (control/update layer)
|
||||
.
|
||||
.
|
||||
└── panelN
|
||||
└── panel-n
|
||||
```
|
||||
|
||||
Continue to [Navigation](Navigation.md) to learn how to switch between panels of a larger app.
|
||||
|
|
|
@ -119,7 +119,7 @@ and inserts its own interceptors
|
|||
so ACTUALLY, there's about 5 interceptors in the chain.
|
||||
|
||||
So, ultimately, that event registration associates the event id `:some-id`
|
||||
with a chain of interceptors.
|
||||
with __just__ a chain of interceptors. Nothing more.
|
||||
|
||||
Later, when a `(dispatch [:some-id ...])` happens, that 5-chain of
|
||||
interceptors will be "executed". And that's how events get handled.
|
||||
|
@ -216,7 +216,7 @@ designed by the talented
|
|||
|
||||
Dunno about you, but I'm easily offended by underscores.
|
||||
|
||||
If our components did this:
|
||||
If we had a component which did this:
|
||||
```clj
|
||||
(dispatch [:delete-item 42])
|
||||
```
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
## Bootstrapping Your Application State
|
||||
## Bootstrapping Application State
|
||||
|
||||
To bootstrap a re-frame application, you need to:
|
||||
1. register handlers (subscription and event handlers)
|
||||
1. register handlers
|
||||
- subscription (via `reg-sub`)
|
||||
- events (via `reg-event-db` or `reg-event-fx`)
|
||||
- effects (via `reg-fx`)
|
||||
- coeffects (via `reg-cofx`)
|
||||
2. kickstart reagent (views)
|
||||
3. Load the right initial data into `app-db` which might be a `merge` of:
|
||||
- Some default values
|
||||
|
@ -11,23 +15,27 @@ To bootstrap a re-frame application, you need to:
|
|||
|
||||
Point 3 is the interesting bit and will be the main focus of this page, but let's work our way through them ...
|
||||
|
||||
## Register Event Handlers
|
||||
## 1. Register Handlers
|
||||
|
||||
Generally, there's nothing to do because this happens automatically at (js) script load time, because you declared and registered your event handlers like this:
|
||||
|
||||
|
||||
```Clojure
|
||||
re-frame's multifarious handlers all work in the same way. You declare
|
||||
and registered your handlers in the one step, like this "event handler" example:
|
||||
```clj
|
||||
(re-frame/reg-event-db ;; event handler will be registered automatically
|
||||
:some-id
|
||||
(fn [db [_ value]]
|
||||
... do some state change based on db and value
|
||||
... do some state change based on db and value ))
|
||||
```
|
||||
|
||||
## Kick Start Reagent
|
||||
As a result, there's nothing further you need to do because
|
||||
handler registration happens as a direct result of loading the code
|
||||
code (presumably via a `<script>`).
|
||||
|
||||
|
||||
## 2. Kick Start Reagent
|
||||
|
||||
Create a function `main` which does a `reagent/render` of your root reagent component `main-panel`:
|
||||
|
||||
```Clojure
|
||||
```clj
|
||||
(defn main-panel ;; my top level reagent component
|
||||
[]
|
||||
[:div "Hello DDATWD"])
|
||||
|
@ -38,17 +46,24 @@ Create a function `main` which does a `reagent/render` of your root reagent comp
|
|||
(js/document.getElementById "app")))
|
||||
```
|
||||
|
||||
## Loading Initial Data
|
||||
Mounting the top level component `main-panel` will trigger a cascade of child
|
||||
component creation. The full DOM tree will be rendered.
|
||||
|
||||
Let's rewrite our `main-panel` component to use a subscription:
|
||||
## 3. Loading Initial Data
|
||||
|
||||
Let's rewrite our `main-panel` component to use a subscription. In effect,
|
||||
we want it to source and render some data held in `app-db`.
|
||||
|
||||
First, we'll create the subscription handler:
|
||||
```Clojure
|
||||
(re-frame/reg-sub ;; a new subscription handler
|
||||
:name ;; usage (subscribe [:name])
|
||||
(fn [db _]
|
||||
(:name db))) ;; pulls out :name
|
||||
|
||||
(:display-name db))) ;; extracts `:display-name` from app-db
|
||||
```
|
||||
|
||||
And now we use that subscription:
|
||||
```clj
|
||||
(defn main-panel
|
||||
[]
|
||||
(let [name (re-frame/subscribe [:name])] ;; <--- a subscription <---
|
||||
|
@ -57,31 +72,33 @@ Let's rewrite our `main-panel` component to use a subscription:
|
|||
```
|
||||
|
||||
The user of our app will see funny things
|
||||
if that `(subscribe [:name])` doesn't deliver good data. We must ensure there's good data in `app-db`.
|
||||
if that `(subscribe [:name])` doesn't deliver good data. But how do we ensure "good data"?
|
||||
|
||||
That will require:
|
||||
1. getting data into `app-db`; and
|
||||
2. not get into trouble if that data isn't yet in `app-db`. For example, the data may have to come from a server and there's latency.
|
||||
2. not get into trouble if that data isn't yet in `app-db`. For example,
|
||||
the data may have to come from a server and there's latency.
|
||||
|
||||
**Note: `app-db` initially contains `{}`**
|
||||
|
||||
### Getting Data Into `app-db`
|
||||
|
||||
Only event handlers can change `app-db`. Those are the rules!! Even initial values must be put in via an event handler.
|
||||
Only event handlers can change `app-db`. Those are the rules!! Indeed, even initial
|
||||
values must be put in `app-db` via an event handler.
|
||||
|
||||
Here's an event handler for that purpose:
|
||||
|
||||
```Clojure
|
||||
|
||||
(re-frame/reg-event-db
|
||||
:initialise-db ;; usage: (re-frame/dispatch [:initialise-db])
|
||||
:initialise-db ;; usage: (dispatch [:initialise-db])
|
||||
(fn [_ _] ;; Ignore both params (db and event)
|
||||
{:display-name "DDATWD" ;; return a new value for app-db
|
||||
:items [1 2 3 4]}))
|
||||
{:display-name "DDATWD" ;; return a new value for app-db
|
||||
:items [1 2 3 4]}))
|
||||
```
|
||||
|
||||
We'll need to dispatch the `:initialise-db` event to get it to execute. `main` seems like the natural place:
|
||||
You'll notice that this handler does nothing other than to return a ` map`. That map
|
||||
will become the new value within `app-db`.
|
||||
|
||||
We'll need to dispatch an `:initialise-db` event to get it to execute. `main` seems like the natural place:
|
||||
```Clojure
|
||||
(defn ^:export main
|
||||
[]
|
||||
|
@ -90,13 +107,21 @@ We'll need to dispatch the `:initialise-db` event to get it to execute. `main` s
|
|||
(js/document.getElementById "app")))
|
||||
```
|
||||
|
||||
But remember, event handlers execute async. So although there's a `dispatch` within `main`, the handler for `:initialise-db` will not be run until sometime after `main` has finished.
|
||||
But remember, event handlers execute async. So although there's
|
||||
a `dispatch` within `main`, the event is simply queued, and the
|
||||
handler for `:initialise-db`
|
||||
will not be run until sometime after `main` has finished.
|
||||
|
||||
But how long after? And is there a race condition? The component `main-panel` (which needs good data) might be rendered before the `:initialise-db` event handler has put good data into `app-db`.
|
||||
But how long after? And is there a race condition? The
|
||||
component `main-panel` (which assumes good data) might be
|
||||
rendered before the `:initialise-db` event handler has
|
||||
put good data into `app-db`.
|
||||
|
||||
We don't want any rendering (of `main-panel`) until after `app-db` is right.
|
||||
We don't want any rendering (of `main-panel`) until after `app-db`
|
||||
has been correctly initialised.
|
||||
|
||||
Okay, so that's enough of teasing-out the issues. Let's see a quick sketch of the entire pattern. It is very straight-forward:
|
||||
Okay, so that's enough of teasing-out the issues. Let's see a
|
||||
quick sketch of the entire pattern. It is very straight-forward.
|
||||
|
||||
## The Pattern
|
||||
|
||||
|
@ -104,12 +129,12 @@ Okay, so that's enough of teasing-out the issues. Let's see a quick sketch of th
|
|||
(re-frame/reg-sub ;; the means by which main-panel gets data
|
||||
:name ;; usage (subscribe [:name])
|
||||
(fn [db _]
|
||||
(:name db)))
|
||||
(:display-name db)))
|
||||
|
||||
(re-frame/reg-sub ;; we can check if there is data
|
||||
:initialised? ;; usage (subscribe [:initialised?])
|
||||
(fn [db _]
|
||||
(not (empty? @db)))) ;; do we have data
|
||||
(not (empty? db)))) ;; do we have data
|
||||
|
||||
(defn main-panel ;; the top level of our app
|
||||
[]
|
||||
|
@ -136,10 +161,10 @@ Okay, so that's enough of teasing-out the issues. Let's see a quick sketch of th
|
|||
|
||||
This pattern scales up easily.
|
||||
|
||||
For example, imagine a more complicated scenario in which your app is not fully initialised until 2 backend services supply data.
|
||||
For example, imagine a more complicated scenario in which your app
|
||||
is not fully initialised until 2 backend services supply data.
|
||||
|
||||
Your `main` might look like this:
|
||||
|
||||
```Clojure
|
||||
(defn ^:export main ;; call this to bootstrap your app
|
||||
[]
|
||||
|
@ -156,17 +181,43 @@ Your `:initialised?` test then becomes more like this sketch:
|
|||
(reg-sub
|
||||
:initialised? ;; usage (subscribe [:initialised?])
|
||||
(fn [db _]
|
||||
(and (not (empty? @db))
|
||||
(:service1-answered? @db)
|
||||
(:service2-answered? @db)))))
|
||||
(and (not (empty? db))
|
||||
(:service1-answered? db)
|
||||
(:service2-answered? db)))))
|
||||
```
|
||||
|
||||
This assumes boolean flags are set in `app-db` when data was loaded from these services.
|
||||
|
||||
## A Cheat - Synchronous Dispatch
|
||||
## Cheating - Synchronous Dispatch
|
||||
|
||||
In simple cases, you can simplify matters by using `(dispatch-sync [:initialise-db])` in the main entry point function. The [Simple Example](https://github.com/Day8/re-frame/blob/8cf42f57f50f3ee41e74de1754fdb75f80b31775/examples/simple/src/simpleexample/core.cljs#L110) and [TodoMVC](https://github.com/Day8/re-frame/blob/8cf42f57f50f3ee41e74de1754fdb75f80b31775/examples/todomvc/src/todomvc/core.cljs#L35) example both use `dispatch-sync` to initialise the app-db. This causes the event to jump to the front of the line and causes it to execute immediately, which is fine for the initial data load in a simple app but can lead to problems elsewhere. As your app gets more complicated, it is strongly suggested that you use the regular `dispatch` function where possible. If you are using `dispatch-sync` and run into weird errors, there's a pretty high chance that it's the culprit.
|
||||
In simple cases, you can simplify matters by using `(dispatch-sync [:initialise-db])` in
|
||||
the main entry point function. The
|
||||
[Simple Example](https://github.com/Day8/re-frame/blob/8cf42f57f50f3ee41e74de1754fdb75f80b31775/examples/simple/src/simpleexample/core.cljs#L110)
|
||||
and [TodoMVC Example](https://github.com/Day8/re-frame/blob/8cf42f57f50f3ee41e74de1754fdb75f80b31775/examples/todomvc/src/todomvc/core.cljs#L35)
|
||||
both use `dispatch-sync` to initialise the app-db.
|
||||
|
||||
`dispatch` queues an event for later processing, but `dispatch-sync` acts
|
||||
like a function call and handles an event immediately. That's useful for initial data
|
||||
load we are considering, particularly for simple apps. Using `dispatch-sync` guarantees
|
||||
that initial state will be in place before any views are mounted, so we know they'll
|
||||
subscribe to sensible values. We don't need a guard like `top-panel` (introduced above).
|
||||
|
||||
But don't get into the habit of using `dispatch-sync` everywhere. It is the right
|
||||
tool in this context and, sometimes, when writing tests, but
|
||||
`dispatch` is the staple you should use everywhere else.
|
||||
|
||||
## Loading Initial Data From Services
|
||||
|
||||
Above, in our example `main`, we imagined using `(re-frame/dispatch [:load-from-service-1])` to request data
|
||||
from a backend services. How would we write the handler for this event?
|
||||
|
||||
The next Tutorial will show you how.
|
||||
|
||||
|
||||
|
||||
---
|
||||
Previous: [Interceptors](Interceptors.md)
|
||||
Up: [Index](Readme.md)
|
||||
Next: [Talking To Servers](Talking-To-Servers.md)
|
||||
|
||||
## Services
|
||||
|
||||
Remember when we used `dispatch` to request the data in our `main` function? What would those event handlers looks like? Let's go to [Talking to Servers](Talking-To-Servers.md) and find out!
|
|
@ -1,13 +1,21 @@
|
|||
## Namespaced Ids
|
||||
|
||||
## What About Event Ids?
|
||||
As an app gets bigger, you'll tend to get clashes on ids - event-ids, or query-ids (subscriptions), etc.
|
||||
|
||||
As an app gets bigger, you'll tend to get clashes on event ids. One panel will need to `dispatch` an `:edit` event and so will another, but the two panels will have different handlers.
|
||||
So how then to not have a clash? How then to distinguish between one edit event and another?
|
||||
One panel will need to `dispatch` an `:edit` event and so will
|
||||
another, but the two panels will have different handlers.
|
||||
So how then to not have a clash? How then to distinguish between
|
||||
one `:edit` event and another?
|
||||
|
||||
Your goal should be to use event-ids which encode both the event itself (`:edit` ?) and the context (`:panel1` or `:panel2` ?).
|
||||
Your goal should be to use event-ids which encode both the event
|
||||
itself (`:edit` ?) and the context (`:panel1` or `:panel2` ?).
|
||||
|
||||
Luckily, ClojureScript provides a nice easy solution: use keywords with a synthetic namespace. Perhaps something like `:panel1/edit` and `:panel2/edit`.
|
||||
Luckily, ClojureScript provides a nice easy solution: use keywords
|
||||
with a __synthetic namespace__. Perhaps something like `:panel1/edit` and `:panel2/edit`.
|
||||
|
||||
You see ClojureScript allows the namespace in a keyword to be a fiction. I can have the keyword `:panel1/edit` even though `panel1.cljs` doesn't exist.
|
||||
You see, ClojureScript allows the namespace in a keyword to be a total
|
||||
fiction. I can have the keyword `:panel1/edit` even though
|
||||
`panel1.cljs` doesn't exist.
|
||||
|
||||
Naturally, you'll take advantage of this by using keyword namespaces which are both unique and descriptive.
|
||||
Naturally, you'll take advantage of this by using keyword namespaces
|
||||
which are both unique and descriptive.
|
||||
|
|
|
@ -46,4 +46,4 @@ A high level reagent view has a subscription to :active-panel and will switch to
|
|||
|
||||
```
|
||||
|
||||
Continue to [Namespaced Keywords](Namespaced-Keywords.md) to reduce clashes on event ids.
|
||||
Continue to [Namespaced Keywords](Namespaced-Keywords.md) to reduce clashes on ids.
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 208 KiB |
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
|
@ -1,4 +1,33 @@
|
|||
**re-frame logo**
|
||||
## The re-frame Logo
|
||||
|
||||
Created by the mysterious @martinklepsch
|
||||
|
||||
Some say he appears on high value stamps in Germany and that he once
|
||||
punched a horse to the ground. Others say that he loves recursion so much
|
||||
that in his wallet he keeps a photograph of his wallet.
|
||||
|
||||
All we know for certain is that he wields [Sketch.app](https://www.sketchapp.com/) like
|
||||
Bruce Lee wielded nunchucks.
|
||||
|
||||
## Genesis Theories
|
||||
|
||||
While we wouldn't presume to fathom the cavernous depths of Martin's creativity, some have
|
||||
speculated the re-frame logo was created as a bifarious rainbow omage to Frank Lloyd Wright's Guggenheim.
|
||||
|
||||
![](Guggenheim.jpg)
|
||||
|
||||
<br><br>
|
||||
Others scoff and insist he smeared the cljs logo across re-frame's official
|
||||
architecture diagram to form a flowing poststructuralist rebuttal of the tyrannical
|
||||
"OO" adjacency.
|
||||
|
||||
|
||||
![](Genesis.png)
|
||||
|
||||
You be the judge.
|
||||
Are we better off never knowing? Does art require an answer?
|
||||
|
||||
### Instructions
|
||||
|
||||
Use [Sketch.app](https://www.sketchapp.com/) to update the `re-frame-logo.sketch` file.
|
||||
|
||||
|
|
Loading…
Reference in New Issue