From 86e6189525fe0c24974133b91ee9690cf1887713 Mon Sep 17 00:00:00 2001 From: Mike Thompson Date: Thu, 25 Aug 2016 18:40:07 +1000 Subject: [PATCH 01/11] A couple of tiny docs tweaks --- docs/Interceptors.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Interceptors.md b/docs/Interceptors.md index 56e4ae0..1ce085b 100644 --- a/docs/Interceptors.md +++ b/docs/Interceptors.md @@ -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]) ``` From 6d11a0116d68d4252b570b35d68fac38f0763a5a Mon Sep 17 00:00:00 2001 From: Mike Thompson Date: Thu, 25 Aug 2016 19:24:00 +1000 Subject: [PATCH 02/11] One pass over loading-initial-data docs --- docs/Loading-Initial-Data.md | 105 +++++++++++++++++++++++------------ 1 file changed, 70 insertions(+), 35 deletions(-) diff --git a/docs/Loading-Initial-Data.md b/docs/Loading-Initial-Data.md index c88cb7f..19f740e 100644 --- a/docs/Loading-Initial-Data.md +++ b/docs/Loading-Initial-Data.md @@ -1,7 +1,11 @@ ## Bootstrapping Your 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,24 @@ 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 +You declare and registered your handlers in the one step like this: +```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, at script (js) load time, the +registration happens automatically. + +## 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 +43,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 +69,31 @@ 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!! Even initial +values must be put in 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: - +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 +102,19 @@ 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 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 needs 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. -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 +122,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 +154,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 +174,34 @@ 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 -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. -## Services +`dispatch-sync` acts like a function call +and ensures the event is handled immediately, which is useful for initial data load in a +simple app - you don't need to guard against uninitialised data - you don't need +a `top-panel` (introduced above). -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! \ No newline at end of file +But don't get into the habit of using `dispatch-sync`. It is the right +tool in this context and, sometimes, when writing tests, but +`dispatch` is the staple to be used everywhere else. + +## Loading Initial Data From Services + +Above, in our example `main`, we used `dispatch` to request data +from a backend service. What would the event handlers look like +which initiate the GETs? + +Let's go to [Talking to Servers](Talking-To-Servers.md) and find out! From 5628ea3f14c69c42db38b4d546ae322448404232 Mon Sep 17 00:00:00 2001 From: Mike Thompson Date: Thu, 25 Aug 2016 21:52:22 +1000 Subject: [PATCH 03/11] Another pass over Loading-Initial-Date doc --- docs/Loading-Initial-Data.md | 60 +++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/docs/Loading-Initial-Data.md b/docs/Loading-Initial-Data.md index 19f740e..cb04499 100644 --- a/docs/Loading-Initial-Data.md +++ b/docs/Loading-Initial-Data.md @@ -1,4 +1,4 @@ -## Bootstrapping Your Application State +## Bootstrapping Application State To bootstrap a re-frame application, you need to: 1. register handlers @@ -17,7 +17,8 @@ Point 3 is the interesting bit and will be the main focus of this page, but let' ## 1. Register Handlers -You declare and registered your handlers in the one step like this: +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 @@ -25,8 +26,10 @@ You declare and registered your handlers in the one step like this: ... do some state change based on db and value )) ``` -As a result, there's nothing further you need to do because, at script (js) load time, the -registration happens automatically. +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 `