devcards
devcards copied to clipboard
Using devcards with re-frame
Devcards is awesome, particularly for beginners or anyone exploring Reagent or Om.
Re-frame is a great way to design large Reagent applications -- however, since there is only one app-db that holds all the state, it may not be obvious how you can use it with devcards.
I found an inelegant way to handle it -- and I'm curious if it would make sense to put it in the wiki
I also think that this pr or something like it might allow for a more idiomatic solution #97 curious of your thoughts.
When you want to see the entire state of your re-frame db to appear in a card
require [re-frame.db :refer [app-db]] in the ns, then put that in as the db of your reagent card.
When you want to only see some component of the state,
Def a subscription to that component of the state, and use that as the card's db
Here's an example:
- Create a handler to update app-db
(register-handler
:demo1
(fn [_ _]
{:status "great"
:number 42}))
this just replaces the app-db with the map
- dispatch that action
(dispatch [:demo1])
;you can deref the app-db to make sure it worked
@app-db
; should be {:status "great" :number 42}
if you have a card that only needs the status, register a sub for that
(register-sub :status (fn [db _](reaction %28:status @db%29)))
;; then def that subscription
(def lildb
(subscribe [:status]))
;; now you have a subset of the db you can include in a card
@lildb
;; should show "great"
(defcard-rg status-card
[:h1 (str "I'm" @lildb "today")]
lildb
{:inspect-data true})
`
But wait -- there is a problem with this -- the problem being that changes to the app-db don't get reflected in the def'd subscription
--- in your devcards use that def'd subscription as the card's db, and in place of where you'd define the subscription in your component
Example
(defn swapper []
(let [s (subscribe [:status])]
[:div
(pr-str @s)
[:button {:on-click #(dispatch [:change-status "fantastic"])} "fantastic"]
[:button {:on-click #(dispatch [:change-status "terrible"])} "terrible"]]))
(defcard-rg status-card
[swapper]
lildb
{:inspect-data true
:history true})
when the dispatch buttons are clicked, the value of @s will change, but not the card's db. Why? The card's db isn't being directly altered, or called from within the card -- so if you want it to match up appropriately, you have to do things like this
(def lildb
(subscribe [:status]))
(defn swapper []
[:div
(pr-str @lildb)
[:button {:on-click #(dispatch [:change-status "fantastic"])} "fantastic"]
[:button {:on-click #(dispatch [:change-status "terrible"])} "terrible"]]))
Unfortunately, while doing that will allow you to walk the history back and forth, the history you're walking back and forth can fall out of sync with the actual app-db -- so you have to be cautious.
Is there a better way to do this?
Would it make sense to decouple subscriptions from components? Something like
(defn swapper []
(let [s (subscribe [:status])]
#(swapper-view @s)))
(defn swapper-view [status]
[:div
(pr-str status)
...])
We'd then use swapper-view
with the devcard.
EDIT: I was previously naming swapper-view
as swapper-component
, but I think the former conveys intent more accurately.
Would cursors work instead of subscriptions?
Hello. I made a separate namespace to host a local state version of re-frame v10.2 some months ago. Basically, one creates a state which holds mostly all containers of re-frame (app-db, kind->id->handler, etc.). Then, inside the app, all calls to reg-sub, reg-event-X, subscribe, dispatch, will use the created state as first parameter.
(defonce state
(-> (new-state)
(reg-sub :db (fn [db _] db)))
...
(reg-event-db .....)))
;; later ...
(dispatch state [:event-x])
(subscribe state [:db])
It appears to work well. I can use devcards with 2 re-frame applications on the same namespace/file. It may be useful for someone.
https://github.com/jfigueroama/re-frame