bhauman / devcards

Devcards aims to provide a visual REPL experience for ClojureScript
1.53k stars 116 forks source link

Using devcards with re-frame #105

Open Conaws opened 8 years ago

Conaws commented 8 years ago

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:

  1. Create a handler to update app-db
(register-handler
 :demo1
 (fn [_ _]
   {:status "great"
     :number 42}))

this just replaces the app-db with the map

  1. 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?

david commented 7 years ago

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.

johanatan commented 7 years ago

Would cursors work instead of subscriptions?

jfigueroama commented 6 years ago

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