mirror of
https://github.com/status-im/re-frame.git
synced 2025-02-23 15:28:09 +00:00
Merge pull request #209 from smahood/master
Update and rename "Bootstrap-An-Application" docs
This commit is contained in:
commit
8452bf741e
172
docs/Loading-Initial-Data.md
Normal file
172
docs/Loading-Initial-Data.md
Normal file
@ -0,0 +1,172 @@
|
||||
## Bootstrapping Your Application State
|
||||
|
||||
To bootstrap a re-frame application, you need to:
|
||||
1. register handlers (subscription and event handlers)
|
||||
2. kickstart reagent (views)
|
||||
3. Load the right initial data into `app-db` which might be a `merge` of:
|
||||
- Some default values
|
||||
- Values stored in LocalStorage
|
||||
- Values obtained via service calls to server
|
||||
- etc, etc
|
||||
|
||||
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
|
||||
|
||||
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/reg-event-db ;; event handler will be registered automatically
|
||||
:some-id
|
||||
(fn [db [_ value]]
|
||||
... do some state change based on db and value
|
||||
```
|
||||
|
||||
## Kick Start Reagent
|
||||
|
||||
Create a function `main` which does a `reagent/render` of your root reagent component `main-panel`:
|
||||
|
||||
```Clojure
|
||||
(defn main-panel ;; my top level reagent component
|
||||
[]
|
||||
[:div "Hello DDATWD"])
|
||||
|
||||
(defn ^:export main ;; call this to bootstrap your app
|
||||
[]
|
||||
(reagent/render [main-panel]
|
||||
(js/document.getElementById "app")))
|
||||
```
|
||||
|
||||
## Loading Initial Data
|
||||
|
||||
Let's rewrite our `main-panel` component to use a subscription:
|
||||
|
||||
```Clojure
|
||||
(re-frame/reg-sub ;; a new subscription handler
|
||||
:name ;; usage (subscribe [:name])
|
||||
(fn [db _]
|
||||
(:name db))) ;; pulls out :name
|
||||
|
||||
|
||||
(defn main-panel
|
||||
[]
|
||||
(let [name (re-frame/subscribe [:name])] ;; <--- a subscription <---
|
||||
(fn []
|
||||
[:div "Hello " @name])))) ;; <--- use the result of the 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`.
|
||||
|
||||
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.
|
||||
|
||||
**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.
|
||||
|
||||
Here's an event handler for that purpose:
|
||||
|
||||
```Clojure
|
||||
|
||||
(re-frame/reg-event-db
|
||||
:initialise-db ;; usage: (re-frame/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]}))
|
||||
```
|
||||
|
||||
We'll need to dispatch the `:initialise-db` event to get it to execute. `main` seems like the natural place:
|
||||
|
||||
```Clojure
|
||||
(defn ^:export main
|
||||
[]
|
||||
(re-frame/dispatch [:initialise-db]) ;; <--- this is new
|
||||
(reagent/render [main-panel]
|
||||
(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 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:
|
||||
|
||||
## The Pattern
|
||||
|
||||
```Clojure
|
||||
(re-frame/reg-sub ;; the means by which main-panel gets data
|
||||
:name ;; usage (subscribe [:name])
|
||||
(fn [db _]
|
||||
(: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
|
||||
|
||||
(defn main-panel ;; the top level of our app
|
||||
[]
|
||||
(let [name (re-frame/subscribe :name)] ;; we need there to be good data
|
||||
(fn []
|
||||
[:div "Hello " @name]))))
|
||||
|
||||
(defn top-panel ;; this is new
|
||||
[]
|
||||
(let [ready? (re-frame/subscribe [:initialised?])]
|
||||
(fn []
|
||||
(if-not @ready? ;; do we have good data?
|
||||
[:div "Initialising ..."] ;; tell them we are working on it
|
||||
[main-panel])))) ;; all good, render this component
|
||||
|
||||
(defn ^:export main ;; call this to bootstrap your app
|
||||
[]
|
||||
(re-frame/dispatch [:initialise-db])
|
||||
(reagent/render [top-panel]
|
||||
(js/document.getElementById "app")))
|
||||
```
|
||||
|
||||
## Scales Up
|
||||
|
||||
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.
|
||||
|
||||
Your `main` might look like this:
|
||||
|
||||
```Clojure
|
||||
(defn ^:export main ;; call this to bootstrap your app
|
||||
[]
|
||||
(re-frame/dispatch [:initialise-db]) ;; basics
|
||||
(re-frame/dispatch [:load-from-service-1]) ;; ask for data from service-1
|
||||
(re-frame/dispatch [:load-from-service-2]) ;; ask for data from service-2
|
||||
(reagent/render [top-panel]
|
||||
(js/document.getElementById "app")))
|
||||
```
|
||||
|
||||
Your `:initialised?` test then becomes more like this sketch:
|
||||
|
||||
```Clojure
|
||||
(reg-sub
|
||||
:initialised? ;; usage (subscribe [:initialised?])
|
||||
(fn [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.
|
||||
|
||||
## 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,10 +1,10 @@
|
||||
|
||||
## Understanding Event Handlers:
|
||||
|
||||
1. [Effectful Handlers](EffectfulHandlers.md)
|
||||
2. [Interceptors](Interceptors.md)
|
||||
3. [Effects](Effects.md)
|
||||
4. [CoEffects](coeffects.md)
|
||||
1. [Pure Event Handlers] TODO - write something good here
|
||||
2. [Effectful Handlers](EffectfulHandlers.md)
|
||||
3. [Interceptors](Interceptors.md)
|
||||
4. [Effects](Effects.md)
|
||||
5. [CoEffects](coeffects.md)
|
||||
|
||||
|
||||
## Structuring Your Application:
|
||||
@ -14,7 +14,8 @@
|
||||
3. [Namespaced Keywords](Namespaced-Keywords.md)
|
||||
|
||||
|
||||
## Populating Your Application Data:
|
||||
|
||||
## Tutorials
|
||||
|
||||
1. [Talking To Servers](Talking-To-Servers.md)
|
||||
1. [Loading Initial Data](Loading-Initial-Data.md)
|
||||
2. [Talking To Servers](Talking-To-Servers.md)
|
||||
3. [Dealing With Databases] TODO - Update Subscribing to a Database docs
|
@ -105,10 +105,10 @@ well salted paper cut. We try hard to avoid them.
|
||||
|
||||
The better solution is, of course, to use an effectful handler. This
|
||||
is explained in detail in the previous tutorials: [Effectful Handlers](EffectfulHandler.md)
|
||||
and [Effects](Effects.md].
|
||||
and [Effects](Effects.md).
|
||||
|
||||
We use the alternative registration function, `reg-event-fx` , and we'll use an
|
||||
"Effect Handler" supplied this this library
|
||||
"Effect Handler" supplied by this library
|
||||
[https://github.com/Day8/re-frame-http-fx](https://github.com/Day8/re-frame-http-fx).
|
||||
You may soon feel confident enough to write your own.
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user