fulcro

Book: http://book.fulcrologic.com, Community Resources: https://fulcro-community.github.io/, RAD book at http://book.fulcrologic.com/RAD.html
Mr. Savy 2020-09-14T02:49:37.457400Z

How often, if ever, would you make a nested comp/get-query call?

tony.kay 2020-09-14T14:47:22.459100Z

In every app... I'm not sure you're asking what you think you're asking

Mr. Savy 2020-09-15T03:02:49.499900Z

I guess so

zhuxun2 2020-09-14T05:26:50.457800Z

@tony.kay Hmmm... in that case do I create n versions of AccountQuery, for the n different views an Account is represented as? Otherwise, if I only create just one all-encompassing AccountQuery that has all the 20 things an account has, then wouldn't I be asking too much in a simple Badge?

zhuxun2 2020-09-14T05:30:25.458200Z

@tony.kay and also, when would I get non-normalized account if would never do (load! ... Badge)? The query of Badge will only be used for pulling data from the client db, isn't it?

2020-09-14T08:13:30.458400Z

(defsc UiPhraseLocation [this {:phrase-location/keys [from to id] :as props
                               :ui/keys              [extraction-phrase]}]
  {:use-hooks? true}
  (let [[open? set-open]   (react/useState)
        {:keys [source i]} (comp/get-computed props)
        phrase             (subs source from to)
        loading?           (get-in props [df/marker-table [:fetching id]])]
    ($ ListItem
       {:style #js {:display        "grid"
                    :gridTemplate   "'top-bar' 50px
                                     'ana'     auto / 1fr"
                    :padding        "0px"
                    :background     (if (odd? i)
                                      "rgba(220, 240, 220, 0.31)"
                                      "rgba(200, 230, 200, 0.31)")}
        :key (str id)}
       ($ ButtonBase
          {:onClick #(do (set-open (not open?))
                         (when-not (or extraction-phrase loading?)
                           (df/load! this [:phrase/phrase phrase]
                                     ExtractionPhrase {:target [:phrase-location/id id :ui/extraction-phrase]
                                                       :marker [:fetching id]
                                                       :parallel? true})))
           :style #js {:gridArea      "top-bar"
                       :height        "50px"
                       :justifyContent "space-between"
                       :background (if (odd? i)
                                     "rgba(220, 240, 220, 0.31)"
                                     "rgba(200, 230, 200, 0.31)")}}
          ($ Typography {:variant "h5" :color "secondary"
                         :style #js {:textOverflow "ellipsis"
                                     :overflow     "hidden"
                                     :whiteSpace   "nowrap"
                                     :width        "80%"
                                     :position     "absolute"
                                     :left "15px"}} phrase)
          ($ :div {:style {:position "absolute" :right "15px" :top "15px"}}
               (if open? ($ ExpandLess) ($ ExpandMore))))
       ($ Collapse
          {:style #js {:gridArea "ana"}
           :in open? :unmountOnExit true :timeout "auto"}

          (if loading?
            ($ LinearProgress {})
            (ui-extraction-phrase extraction-phrase))
          #_(for [[i [form short long]] (map-indexed vector forms)]
              ($ Typography {:variant "h6"  :color "secondary" :key (str i " - " form " - " i)
                             :style #js {:alignSelf "center"}}
                 (str form " - " short " - " long )))))))

(form/defsc-form PhraseLocation [this {:phrase-location/keys [from to id] :as props}]
  {::form/attributes [extr/phrase-loc-from
                      extr/phrase-loc-to
                      extr/phrase-loc-id]
   ::form/query-inclusion [{:ui/extraction-phrase (comp/get-query ExtractionPhrase)}
                           [df/marker-table '_]]
   ::form/id          extr/phrase-loc-id
   :use-hooks?        true})

(def ui-phrase-location (comp/factory UiPhraseLocation {:keyfn :phrase-location/id}))

(form/defsc-form Extraction [this {:extraction/keys [phrase-locations source]}]
  {::form/id         extr/id
   ::form/attributes [extr/source extr/phrase-locations extr/id]
   ::form/subforms   {:extraction/phrase-locations
                      {::form/ui PhraseLocation}}
   :use-hooks? true}
  (map-indexed (fn [i phrase-location]
                 (comp/with-parent-context this
                   (ui-phrase-location (comp/computed phrase-location {:i i :source source}))))
               phrase-locations))

2020-09-14T08:15:39.458600Z

use-hooks? seems not to work on defsc-form so I made a different component for that. The issue seemed to stem from the map-indexed I thought maybe the Collapse was suspect, but the render of UiPhraseLocation didn't seem to matter.

2020-09-14T08:20:04.458800Z

not sure if it matters but the Extraction gets rendered with a comp/factory -> ui-extraction like so:

(when (:material/extraction selected-material) 
  (ui-extraction (:material/extraction selected-material)))

tony.kay 2020-09-14T14:47:22.459100Z

In every app... I'm not sure you're asking what you think you're asking

tony.kay 2020-09-14T15:58:00.460200Z

map-indexed is lazy, and since it is at the top of the body it isn’t getting forced. I bet if you put it in a div it’ll be fine. The DOM elements all forcechildren, but defsc does not. That’s technically an oversight on my part, but I guess no one has tried it quite that way

tony.kay 2020-09-14T15:59:14.460500Z

Yes, you write different components with different queries, but using the same Ident. That’s most of the point, remember? Being able to ask for what you need, when you need it?

hmaurer 2020-09-14T17:34:35.462600Z

@tony.kay Hello! I went on a walk this evening and I was pondering about Fulcro. I got reminded that when I first started playing with Fulcro I found the idea of normalising data via component queries neat, and it made perfect sense for examples I could come up with (e.g. a Todo component mapping to a Todo entity, etc), but it wasn’t obvious to me (and still isn’t) that the model generalised (e.g. that I wouldn’t have a component corresponding to two distinct data nodes, etc). Have you found this to be a limitation or a problem in practice?

JAtkins 2020-09-14T18:29:26.463700Z

Could you clarify? Are you saying that if you have some {:todo/id 1 :todo/title "..."} on the screen you wouldn't expect fulcro to be capable of drawing it on the screen twice?

tony.kay 2020-09-14T19:09:39.467400Z

@hmaurer no. The model generalizes in the following ways: • Two different things that need to display in a hetero list (e.g. a timeline feed with news/images/etc): Union queries • A component that has a mix of data from various places in the real back-end db: Keywords are namespaced. Query resolution can combine disparate things into a view, and save logic can pick them back apart. Anything that can displayed this way must by its very nature be to-one in relation (otherwise you’d have to have an edge to traverse, which would lead to children with different IDs). Thus, merging data implies that you can “merge IDs”…e.g. :account/id and :primary-address/id can co-exist in a single component even though more than on ID corresponds to elements on that UI. Save, of course, has to know that fields need what IDs. RAD solves that by having you declare what identities (a keyworkd like :account/id) identifies the entity(ies) on which that fact can be saved or read.

1❤️
hmaurer 2020-09-14T20:19:01.469600Z

@tony.kay thanks for writing this up. Follow-up question, then: how would you deal with, say, a “CompareVehicles” component which lets you pick two vehicles (entities in the DB) and compare them (imagine a side-by-side table).

tony.kay 2020-09-14T20:20:38.470200Z

a container with either a single to-many join (with 2 elements) or two edges that I made up :left-car :right-car

tony.kay 2020-09-14T20:21:02.470700Z

[{:left-car (comp/get-query Car)} {:right-car (comp/get-query Car)}]

tony.kay 2020-09-14T20:21:18.471Z

or [{:cars-to-compare (comp/get-query Car)}]

tony.kay 2020-09-14T20:21:27.471300Z

the latter supports n-compare screen

1💯
Björn Ebbinghaus 2020-09-14T20:56:37.473Z

I am getting following message twice (same Router) on startup since I updated to 3.2.16:

WARN [com.fulcrologic.fulcro.ui-state-machines:215] -  #error {:message "", :data {}} Attempt to get an ASM path [:com.fulcrologic.fulcro.ui-state-machines/local-storage :path-segment] for a state machine that is not in Fulcro state. ASM ID:  :decide.ui.root/PageRouter
Fulcro 3.2.15 works fine. 3.2.17 breaks even more after that… Do I have to initialize my routers? I remember that it wasn’t necessary anymore sometime ago.

tony.kay 2020-09-14T21:52:06.473700Z

“breaks” or gives warning?

Björn Ebbinghaus 2020-09-15T11:28:03.004900Z

With .16 only these warnings and the page renders like expected.

Björn Ebbinghaus 2020-09-15T11:28:14.005300Z

With .17 the page does not render.

Björn Ebbinghaus 2020-09-15T11:29:07.005700Z

I deleted the .shadow-cljs folder and restarted shadow-cljs after each change

Björn Ebbinghaus 2020-09-15T11:38:39.006Z

I don’t have much time today. I’ll take a closer look at this later.

tony.kay 2020-09-15T15:12:37.006500Z

OK, that helps. I did change that function. I see where the crash is happening, and I can protect that code, but the old version was just better at tolerating the overall problem, which I suspect is either a mis-use (or mis-documentation) of the dynamic routers themselves. Try 3.2.18-SNAPSHOT when you have a moment.

tony.kay 2020-09-15T15:13:26.006700Z

That sequence in your main app from line 60 to 57 to 35…what is that sequence? The crash you’re seeing is because something in the app wasn’t yet initialized. I think the new version of that function was just being less tolerant of being called when things weren’t initialized. Hopefully the snapshot version I pushed fixes that, but I should update the dyn routing chapter and be more explicit about how to initialize it properly.

Björn Ebbinghaus 2020-09-15T16:25:17.008300Z

in 18-SNAPSHOT there are only the two warnings from .16 Line 35 is a call to dr/current-route The component used as the parameter is a router-target for my root level router (called PageRouter)

Björn Ebbinghaus 2020-09-15T16:30:58.008500Z

Following code does not help with the issue in .17 & .18-SNAPSHOT

(defn ^:export init []
  (log/info "Application starting.")
  (app/set-root! SPA root/Root {:initialize-state? true})
  (dr/initialize! SPA)
  (app/mount! SPA root/Root "decide" {:initialize-state? false}))
It changes nothing. Same warnings / errors.

Björn Ebbinghaus 2020-09-15T16:40:53.008700Z

The warnings are coming from a dr/change-route! called in client-did-mount after my HTML5 routing has started.

Björn Ebbinghaus 2020-09-15T16:41:25.008900Z

Well not directly called. the dr/change-route is in the callback for pushy

tony.kay 2020-09-14T22:20:58.475400Z

There is sort of a race condition between initial state (which has no state machines in it) and any attempt to find the “current route”. That is what initialize! is about: it tries to make sure the machines are started. Ideally, the real initialization is to do a sequence like this:

(app/set-root! SPA Root {:initialize-state? true})
(dr/initialize! SPA)
(app/mount! SPA Root "app" {:initialize-state? false})

tony.kay 2020-09-14T22:21:34.476100Z

that will prevent the UI from looking for the current route before there is one…ideally you’d also change-route! before the mount as well

tony.kay 2020-09-14T22:22:02.476700Z

I need to update the book…it is possible my recent cleanups show more warnings than before, but should not have “broken” anything

xceno 2020-09-14T22:47:13.486900Z

Could someone give me a quick sanity check?: I've roughly the following attributes/entities:

(defattr category-id :category/id :uuid {ao/identitiy? true})
(defattr element-id :element/id :uuid {ao/identity? true})

(defattr content-id :content/id :uuid {ao/identity? true ao/identities #{:category/id :element/id})
So I've go a bunch of different entities and want to provide a "global" content-id, so I can model edges with metadata, that can connect all the entities in my system. But how can I make sure both, my entity-ids and the content-id are created when saving a new instance via a RAD form? (Or maybe what I'm trying to do is a stupid idea to begin with?)

Björn Ebbinghaus 2020-09-14T23:19:55.491500Z

In .16 i just got the message from above. In .17 it is followed by a lot of red on my console something about deref of nil in current-route. No render anymore. I am currently not on my computer. I will look for details tomorrow.