Fork me on GitHub
#fulcro
<
2022-08-09
>
sheluchin17:08:34

I'm trying to do some syntax highlighting on the frontend using https://prismjs.com/index.html. My project is based off the RAD template. The highlighter works well when I put snippets above the router, but if I put some snippet dom inside the router or anywhere in its nodes, the highlight doesn't work. Anyone know what might be causing that? PrismJS basically works by including a stylesheet in the header and a js file in the footer, and just tagging your desired DOM nodes with language-specific classes where you want the text highlighted.

sheluchin17:08:47

(defrouter MainRouter [_this {:keys [current-state route-factory route-props]}]
  {:always-render-body? true
   :router-targets
   [MyReport]}
  (div
   (dom/pre {:data-start 99}
            (dom/code :.language-clojure
                      "(defn "
                      (dom/mark "bar")
                      " [x] (println x))")))

sheluchin19:08:22

If I change the (if ready? to just (if true the rendering always just works 😕

(defrouter TemplateMainRouter [this {:keys [current-state route-factory route-props]}]
  {:always-render-body? true
   :router-targets      [LandingPage]}
  ;; Normal Fulcro code to show a loader on slow route change (assuming Semantic UI here, should
  ;; be generalized for RAD so UI-specific code isn't necessary)
  (ui-foo)
  #_
  (dom/div
    (dom/div :.ui.loader {:classes [(when-not (= :routed current-state) "active")]})
    (when route-factory
      (route-factory route-props))))

(def ui-template-main-router (comp/factory TemplateMainRouter))

(defsc Root [this {:ui/keys [ready? router]
                   ::app/keys [active-remotes]}]
  {:query [:ui/ready?
           {:ui/router (comp/get-query TemplateMainRouter)}
           ; [::uism/asm-id ::MainRouter]
           ::app/active-remotes]
   :initial-state {:ui/ready? false
                   :ui/router {}}}
  (if #_ ready? true ; always works
    (div
      (ui-template-main-router router))
    (div :.ui.active.dimmer
      (div :.ui.large.text.loader "Loading")))
Still don't understand what's happening here, but I suppose it's a good clue.

Jakub Holý (HolyJak)09:08:05

I guess the route target is not yet routed to and thus rendered when prism runs. It would help if prism had some api tu run it manually so you could delay that.

sheluchin12:08:00

@U0522TWDA thanks very much for the hint! Indeed, there is an API for that and it looks like it works from my first REPL test. What would be the right place to call the highlighting function? I'm guessing inside :componentDidMount.

Ernesto Garcia17:08:19

[beginner] Hi. I have a component which state is a list of objects (maps). I'm using a query which is a vector of keywords. Shouldn't this query result in a projection of the list of objects in the state?

(defsc MyList [this props]
  {:query (fn [] [:id :name])
   :initial-state (fn [_] [{:id 1 :name "One"}
                           {:id 2 :name "Two"}])}
  (dom/div))

(comment
  (fdn/db->tree
    (comp/get-query MyList)
    (comp/get-initial-state MyList)
    {}) ; => {}
  ,)

tony.kay17:08:13

db->tree has a weird signature. The args are: 1. the query for the starting entity 2. the starting entity itself 3. The state map You’re passing it: 1. The query for an entity 2. a vector of data (joins are the only place to-many are expected, and your query and tree are NOT a join) 3. Nothing You would need a root node with a join to get this into proper shape

tony.kay17:08:46

And you would actually use tree->db to make the db, and then you can reverse the process with db->tree

tony.kay17:08:02

I think I show this is some of the grokking Fulcro videos? And in various other places.

Ernesto Garcia17:08:42

I got it from the tutorial:

Ernesto Garcia17:08:00

Anyway, I was just trying to illustrate that the query of my list component is actually not getting anything. I'm being passed nil props.

tony.kay17:08:21

99% of “getting Fulcro” is query+ident+initial state. If you show the actual code you’re using, I can point out your error. But it is really really simple (most ppl make it WAY harder than it actually is): 1. component queries are composed into a big huge graph (component queries do NOT live in isolation) 2. Initial state HAS to exactly match the shape of (1) so that it can be normalized (via the ident function and tree->db) into a graph db that has the EXACT structure to fulfill (1) via db->tree 3. Fulcro runs the query (from root) against the graph. On the surface this seems a bit crazy. You literally use a mirrored tree of data and a graph query to create the db, then pull the original tree back out to feed to the UI. The whole purpose, though, is to get it normalized. IF you’re thinking that a component query should “just work” in any context by some magic, then you will be confused. That is not how it works.,

Ernesto Garcia17:08:29

Yes, it was working until I introduced this list component, which query fragment I understood was to be a vector of keywords. Am I right in understanding that such a query fragment should work? Trying again, I will post the code if I fail again.

Ernesto Garcia17:08:04

How can I check that the compound query against the compound initial-state?

Ernesto Garcia18:08:05

I am seeing that the props of a component must always be a map, so one can't define a component that admits a list as its state, right?

tony.kay18:08:46

The initial state of a component should always be a map, correct. When you want to do a to-many, that should be represented in the intitial state of a component at the join (in your case at the root).

tony.kay18:08:15

the basic check is: (get-query Root) is compatible with (get-initial-state Root {})

tony.kay18:08:43

so, the query of a list of things might be [{:things (comp/get-query Thing)}], and the initial state could EITHER be a to-one or to-many relation…the query doesn’t determine that, your data does: :initial-state {:things {:id :a}} is one, and :initial-state {:things [{:id :a} {:id :b}]} is many

👍 1
tony.kay18:08:34

of course if you’re doing proper composition then the nested items should be calls to get-initial-state, but if you look in the docs around initial state, you’ll see that the non-function version of initial-state is “magic” and does that for you based on query analysis

Ernesto Garcia18:08:00

I must run the docs sample again. I'm introducing idents to my own component hierarchy, but I don't see my initial-state being normalized.

Ernesto Garcia18:08:36

Thanks for your help. Appreciate the time.

genekim17:08:45

Is there a way to set an :initial-state in a Fulcro RAD form? I’m trying to set some custom state (e.g., :ui/show-word-cloud?), and trying to figure out where to set it. (I discovered componentDidMount is not the right place, as the report might get reloaded with a different route/id/etc, which won’t unmount/remount the component. So, in the meantime, I am manually calling a mutation event before loading the report, which works, but seems error-prone.) Thank you!

tony.kay17:08:58

Hey Gene. So forms can have default values for form fields, but by default don’t have “initial state” beyond that. The easiest way to do what you want is to augment the state machine for the form. Add an alias for your new prop, include the prop in the query inclusions, and the add it to the proper handlers in various spots in the state machine. Here’s a helper function:

(defn augment-event
  "Add `additional-action` to the end of the actions performed by the `base-machine` in `state` for `event`. `event` can
  be nil if there is just a global handler for that state. Returns the modified machine, so more than one of these
  can be threaded together to modify a machine in multiple places."
  [base-machine state event additional-action]
  (let [handler-path     (if event
                           [::uism/states state ::uism/events event ::uism/handler]
                           [::uism/states state ::uism/handler])
        original-handler (get-in base-machine handler-path)]
    (assoc-in base-machine handler-path (fn [env]
                                          (-> env
                                            (original-handler)
                                            (additional-action))))))

tony.kay17:08:25

Then set the form to use your new state machine with fo/machine

tony.kay17:08:15

E.g.:

(uism/defstatemachine custom-form-machine
  (-> form/form-machine
    (update ::uism/aliases assoc :show-cloud? [:actor/form :ui/show-word-cloud?)
    (augment-event :state/loading :event/loaded some-uism-env-function)
    (augment-event :state/saving :event/saved some-uism-env-function)))

tony.kay17:08:27

Just look at the existing state machine to figure out where you’d want to augment

tony.kay17:08:59

Your env function can then use your new alias:

(defn new-env-f [env]
  (uism/assoc-aliases env :show-cloud? true))

genekim18:08:09

Thank you, @tony.kay! I’ll study this, and keep you posted. 1. Does it change the answer if I meant to say this was for a RAD Report, not a RAD Form? (I suspect no, since this is all done thru state machines.) 2. I just knew you were going to suggest using state machines to do this. 🙂

tony.kay18:08:51

Same answer for both

tony.kay18:08:46

The lifecycle of the data in RAD is always controlled by the state machines. The exception with reports is that you could declare your thing as a control, in which case you can give that an initial value, which can be overridden by incoming route params

genekim18:08:50

As a control! Yes! That's what I should actually do. Thanks!!!!

👍 1
genekim18:08:25

https://clojurians.slack.com/archives/C68M60S4F/p1660069306886419?thread_ts=1660065105.976959&amp;cid=C68M60S4F This is what I love about Fulcro and Fulcro RAD — there are so many thoughtful affordances and mechanisms to implement things. There’s a lot to learn and familiarize yourself with, but I’ve found that the payoff is huge. Thank you, @tony.kay !

👍 1
1
❤️ 1
Jakub Holý (HolyJak)09:08:02

I am sure I am going to quote you saying this somewhere :-)

Ernesto Garcia21:08:41

Is it acceptable to defsc a component that I don't actually use in the UI, only to cause proper db normalization through its query and ident? (I use a React Table component that accepts data as a JS array, not as children Row elements).

tony.kay00:08:33

There is also the new helper in raw.components: nc , which returns a component with an unnamed and unregistered component with a query and ident. It’s a little more terse, but useful if you use the preferred naming conventions: (def X (rc/nc [:thing/id :thing/name])) is the same as (defsc X [_ _] {:query [:thing/id :thing/name] :ident :thing/id})

tony.kay00:08:57

be careful not to use those mixed in with UI concerns unless you give them a :componentName option, since they have to be registered to work properly everywhere…but standalone for just loading or something they are fine.

Ernesto Garcia15:08:17

Thank you for the info and resources!

Jake Murphy23:08:59

I recently switched from using cljsjs/react and cljsjs/react-dom to npm react and react-dom. I am using figwheel-main and webpack to compile cljs and bundle the js. I have no troubles with :simple compilation; however, :advanced compilation is failing, ie "TypeError: Cannot read properties of undefined (reading 'call')". It renders the Root component once, but later fails and I have the console log: "Optimized render failed. Falling back to root render." I would like to know if the inferred externs generated by the compiler are sufficient. I am not using react or react-dom directly. Does anyone have an externs file that would fully account for how fulcro uses react and reactdom?

tony.kay00:08:27

Fulcro’s DOM nses with shadow-cljs and npm packages works flawlessly, and is what is officially supported. I use advanced compilation with all of my projects and have never had a problem.

tony.kay00:08:57

The DOM stuff is super-optimized and uses a lot of low-level js stuff to get the best possible performance. I would love to know if you find an issue that the stock compiler cannot handle, but if you look at the Fulcro DOM namespaces you’ll see that there are not many calls to the low-level react stuff.

tony.kay00:08:34

Try compiling with advanced with source maps. That might help you pinpoint problems.

Jake Murphy14:08:33

Thank you for letting me know about source maps. I learned more with source maps but I'm not sure what is causing the error. All is well until an input element is rendered. Fulcro dom's wrap-form-element contains

{:onChange (goog/bind (gobj/get this "onChange") this)}
compiles to
{onChange:$goog$bind$$($goog$object$get$$(this, "onChange"), this)}
and goog.bind calls goog.bindNative (should it be calling bindNative? as opposed to bindJS) and throws an error suggesting that the call to goog.object.get returns null. The input element has appropriate values for :onChange, :type, and :value and it works for :simple compilation. Thinking this is related to goog, I have tried both the most recent version of clojurescript and the version which the latest fulcro references. Both result in the same error. Any insight into this problem would be appreciated.

Jake Murphy15:08:42

I am going to try shadow-cljs and see how long it takes to setup.

Jake Murphy17:08:31

Shadow was easy to setup and advanced compile worked right away.

👍 1
tony.kay16:08:46

When in doubt go with the supported configuration 😄