This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-09-23
Channels
- # babashka (2)
- # babashka-sci-dev (401)
- # beginners (10)
- # biff (6)
- # calva (6)
- # clj-on-windows (6)
- # cljfx (13)
- # clojure (91)
- # clojure-austin (1)
- # clojure-europe (6)
- # clojure-norway (11)
- # clojurescript (14)
- # clr (3)
- # docker (3)
- # emacs (2)
- # fulcro (21)
- # hyperfiddle (2)
- # membrane (30)
- # nbb (4)
- # pedestal (7)
- # polylith (16)
- # reitit (1)
- # sci (4)
- # xtdb (9)
@tony.kay is there some special reason why com.fulcrologic.fulcro.dom/render-to-str
invokes js/ReactDOMServer.renderToString
instead of having (:require ... ["react-dom/server" :as react-dom-server])
and invoking react-dom-server/renderToString
? I am asking b/c I am getting problems with js/ReactDOMServer
being undefined (in my SCI experiments) 🙏
Do you know how would I do that? Something as hacky as (set! (.-ReactDOMServer js/window) #js {:renderToString react-dom-server/renderToStrin})
?
Yeah :) Or (js/Reflect.set js/window "foo" 2)
if you want to avoid any advanced issues
because we used to use cljsjs, and the compiler has gotten better, and we’ve never fixed it 😄
Hi everyone!
For learning purposes, I'm trying to use Fulcro to recreate an old vanilla Reagent App I once wrote (see screenshot; nowadays I work with Re-frame on daily basis). Part of the app is about apartments with fields like :base-price
, :minimal-stay
, :default-number-of-occupants
and so on. I need to edit this apartment data in an HTML table where each column represents an apartment and each row represents a field, such that each inner cell can hold an input field to manipulate one field for a given apartment. At this point, I'll be happy to just render the values and am not concerned with the transactional aspects.
The way HTML tables work, won't allow me to make an Apartment
component holding the cells for each field and map that over the Apartments. Instead I'll have to make a Cell
component that can be parameterized as to which of the fields it targets and map that one instead (Making separate components for BasePriceCell
and so on would be ridiculous). It's the parameterization part I cannot quite figure out.
I have understood that I could use a computed-factory
to pass the field key from a parent Row
component. But the Cell
component would still have to query for every field of it's apartment to then only use the one corresponding to the key passed from the parent. This feels wrong… and makes me suspect that I at least would get unnecessary re-renders. The queries should probably be dynamic and I have found the https://book.fulcrologic.com/#DynamicQueries, but it looks quite daunting and advanced… too advanced for something so basic (and trivial in Re-frame, cough). I feel I'm falling off the bandwagon here and wanted to inquire as to what would be the Fulcro way of tackling a problem like this…
(defsc Apartment [this {:apartment/keys[price minstay]}]
{:query [:apartment/id :apartment/price :apartment/minstay]
:ident :apartment/id}
(dom/tr
(dom/td
(str price))
...))
would be the row componentThen the parent would be:
(defsc Apartments [this {:list/keys [children]}]
{:ident (fn [] [:component/id ::apartmenttable])
:query [{:list/children (comp/get-query Apartment)}]}
(dom/table
...
(dom/tbody
(mapv ui-apartment children))))
and you’d need a resolver that can get the apartments, perhaps on resolver key :apartments/all or :apartments/search, so you could issue a load like:
(df/load! app :apartments/all Apartment {:target [:component/id ::apartmenttable :list/children]})
making the individual fields inputs is trivial. Everything is normalized, so updating their values is a simple (m/set-value! …)
, though if you want to update those in a remote database you’ll of course need to write a mutation that includes remote capabilities.
instead of (str price) you’d use something like:
(dom/input {:value (str price) :onChange (fn [evt] (m/set-integer! this :event evt))})
Thank you so much for taking the time to reply, I really appreciate it. Your code is actually quite similar to what I already have (save for the resolver part; currently I'm setting the state atom by hand during initialization, so I can focus exclusively on the frontend for now.)
Maybe I haven't expressed my problem well enough, but it wouldn't even arise if each apartment could be a row. My problem specifically stems from the fact that the apartments need to be columns, but there are no column
elements in HTML tables, which makes it hard to honor the Fulcro rule of having a 1:1 relation between data entities and UI elements: It seems to prevent me from simply making an Apartment
Component as you suggest (and as I successfully have, albeit in a list, not a table).
Due to the way HTML tables work, my “components and data entities diverge”, but none of the https://blog.jakubholy.net/2020/fulcro-divergent-ui-data/by @U0522TWDA seem to fit mine directly and all leads I could find in the guide seemed way too advanced for me at this stage (dynamic queries, see above).
I hope to have expressed my issue more clearly.
Thanks again for your reply and, of course, your work on Fulcro, which I'm determined to finally grok (3rd attempt).
I think I would use the code above, for loading/normalization (drop the render body of Apartment). Render by default happens from root, and React/pure components are leaned on to make it fast, and I rarely see that fail to work. So, make a queryless component (of just a fn, would be better, actually) for your Row, and pass it the list of apartments and the aspect you want to render (the apartments are pre-sorted). Simple as that. You’re right that it will regen vdom for everything, but unless you’re doing 1000s of apartments (which you aren’t because they won’t fit that way) it is going to be fast enough.
I think you’re suffering from premature optimization. Release-mode React is hella fast
If that has a speed problem, then in a let
within the render pre-compute the row’s data, and make Row a component that takes that data. That will let React skip vdom for the rows that have not changed. The computation of the rows is on-the-fly during render, but again, unless it is a very large table I doubt you will see a problem.
If that is insufficient, then you could generate a ui view of the data…I’ll send an example in a bit.
Since it’s a “harder than normal case”, I figured adding a card that @U0522TWDA could maybe include in some of his materials would be useful. I intentionally made a LOT of cells so that it would be a scary table for React. I also included a cell render counter, so you can see that it is as optimal as can be….and is also using the normalized apartments so that there is no actual data denormalization…just a view into the normalized data.
I emulated a server so you could see how that integration would look. That’s pretty much the idea of Fulcro in general. Make data that looks like you want the UI to look. For convenience sometimes it is easy and plenty fast to do what I said earlier, but you’re right if a table like this gets big, it is better to normalize the data into a view for optimal interactive performance.
Many thanks again for your detailed replies, which are not only reassuring and practically helpful, but also give some further conceptual insight. I currently use Re-frame at work and for a number on side projects – in the latter realm is subject to change since the freedom offered by Re-frame can easily become quite messy and requires convention and discipline inside a team to stay manageable. This is precisely what I find appealing about Fulcro and – as I've said – I'm determined to make it this time. It may be a bit unfortunate that my fist application is based on HTML tables, which are a bit at odds with one of Fulcro's principles, but it would seem that I have less to worry about than I had thought. I only just saw your second reply and the gist… wow! I surely wasn't expecting you to put so much work into this. I've gone over it and, while I don't quite understand everything yet, I recognize a lot from your videos and many small pieces of more intricate information, like how to confine a re-render to a single component. It definitely shows that one can introduce flexibility where needed. I never feared my application might have become too slow without any such optimizations. I was just wondering if I misunderstood or missed something basic. Thank you so much again… I'm looking forward to the weekend when I'll be able to really dive back into it!