Fork me on GitHub

Hi all, what is the idiomatic way in Fulcro to create a “private” route? I see :allow-route-change? but I don’t see any access to the app-db there. I’d basically be looking to see if we have a token or not, and if we don’t redirect to a login page.


Also, given those semantics I wonder how a route can be protected at any point of entry. As :allow-route-change? implies change from the current route to another.


Or would :will-enter + route-deferred be enough?

(defsc Protected [this props]
  {:ident         (fn [] [:component/id ::protected])
   :query         [:protected]
   :initial-state {:protected nil}
   :route-segment ["protected"]
   :will-enter (fn [app route-params]
                 (if (not (has-token? app))
                   (dr/route-deferred [:component/id ::protected] #((dr/change-route app ["main"])))))}


Fulcro 3.2.17 on Clojars. This release includes a more completely tested sync transaction processing plugin. Still considered slightly experimental, so please use heavily in dev before considering in production. @bbss Book has been updated. I changed how you install it, since it needs to modify a number of things in the app definition now. See I’ve tested it against the Fulcro RAD demo, which is rather heavy hitting on full-stack features with nested transaction submission and it seems fine.


Alright, pulled and now the issue I had before is gone! Thanks, will keep testing this feature and let you know if anything breaks vs not wrapping the app.


@mruzekw you could take a look at how I coded that sort of thing in the RAD demo. Initialization is really a separate step from general operation, and I think it should be treated as such. Trying to make one thing do many things is generally a path to confusion. Your network layer will protect the data, so really what you’re asking is “can I wait to do the routing until I’ve figured out auth?“…which of course requires you to know what you’re doing with auth, how you’re handing HTML5 routing, etc etc. There is no “one way”, nor can I code a pre-built solution into the general library…thus why it is in a demo, where I have the auth and HTML5 routing pinned down.


You could (and should) pre-route to some kind of initial page like loading or landing, then once your session check has returned eval the URL and attempt to updat ethe route, but that also involves capturing the URL before your routing system changes it.


Again, see RAD demo


@aramz Same comment: See RAD

Jakub Holý (HolyJak)13:09:41

BTW I believe the Authorization isn't really flashed out yet.


it is not fully. I would not even consider that alpha…it’s a whole complete thing of its own that I don’t have time to deal with


it’s more in the demo just as an example


Okay, thanks for explaining. Will give that a look


also the book


“can I wait to do the routing until I’ve figured out auth?” Yes, this is exactly my question :thumbsup:


Thanks again


For a state component whose query I never use for loading (and only for read-only querying,) is it okay for me to inline the whole link query? For example is it okay to do:

(defsc Badge [this props]
  {:ident (fn [] [:component/id :badge])
   :query [{[:component/id :current-session] [{:session/account [:account/id :account/name :account/avatar]}]}]}
  (div ...))
As opposed to creating a one-off component to replace the [:account/id :account/name :account/avatar] with (comp/get-query ...)?


If accounts are normalized, then I would not do that: you’ll end up with a non-normalized account stuck in there, and then when you do a form to let them change their name/avatar, you’ll have to screw with the non-normalized stuff.


but does it “work”? Yeah, it’ll “work” for some value of “reasonably well”


Nested link queries should trigger normalization…you could write an AccoutQuery component with query/ident just for that nested part.


@U0CKQ19AQ 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?


@U0CKQ19AQ 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?


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?


Seems like (comp/get-query ...) is not necessary in link queries as link queries are not used in the normalization process


one difference I have with the synchronous transactions is this error/warning pops up:

timbre_support.cljs:80 ERROR [com.fulcrologic.fulcro.components:858] -  A Fulcro component was rendered outside of a parent context. This probably means you are using a library that has you pass rendering code to it as a lambda. Use `with-parent-context` to fix this.
I guess that stems from a map-indexed in that component, adding with-parent-context there seems to fix it 🙂


I tried it on a MUCH larger app, and it is fine. I’m guessing you had a pre-existing issue there that was not reporting for some reason. Be interested in seeing that code. I am seeing the routing system reporting more issues because of the change in overall operational semantics, esp if you change route to a non-leaf in RAD…will need to track that down, but at the moment it seems harmless.

Adam Helins13:09:11

I get odd behavior in Inspect. When I boot the app, I can see the DB and everything. After +/- 10 seconds, everything disappear and I see only an empty map, nothing in DB explorer either. Nothing changes, nothing reloads, no transactions... what on earth could be the reason?!


So, this can happen for a number of reasons (some still unknown). One I’ve seen is if local state in the Inspect tool ends up with non-serializable data (e.g. you put a lambda or component into app state db). You can try to fix that by right clicking on Inspect and Inspecting the Chrome plugin itself, then go to application section and clear local data (local storage etc). You can also reinstall the plugin to clear that. If it is working for a little bit and then dying, could be the same sort of thing..something in an early tx hits a bug I have not yet found. Again, Inspecting Inspect in Chrome’s devtools and looking at that console (not your primary one) might yield more insight: . Right-click within Fulcro Inspect’s tab area and choose “Inspect”, that will pop open a new window with Chrome devtools running against Fulcro Inspect. . Look at the console tab, and Application tab’s local storage

👍 3

@adam678 I sometimes see exactly the same thing as well. It usually happens when there's a lot of transations going on. Be curious to know what exactly causes it.

Adam Helins17:09:27

@zhuxun2 Here is the thing, there are basically a couple of simple transactions at boot, really not much going on. Usually it performs fine under heavy workload


@bbss hm, that’s interesting. What is the actual rendering code causing that?


(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
   ::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}))))


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.


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)))


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


alright, makes sense! cheers.