fulcro

Book: http://book.fulcrologic.com, Community Resources: https://fulcro-community.github.io/, RAD book at http://book.fulcrologic.com/RAD.html
yubrshen 2021-01-28T05:19:00.083400Z

Not able to achieve the only rendering of person 1 At Fulcro 3 Part 5 How Rendering Works, around 13:25, Tony showed that only “Render person 1” with the default (ident-optimized-render), when executing:

(comp/transact! APP [(make-older {:person/id 1})])
But Using the exact copy of Tony’s code from https://github.com/fulcrologic/video-series/blob/how-rendering-works/src/app/client.cljs I still got the following:
Render root
client.cljs:59 Render list
client.cljs:33 Render person 1
I also tried to explicitly use the ident-optimized-render with the following code, which is almost identical to Tony’s, but still got the same rendering of more than the person 1’s.

yubrshen 2021-01-28T05:19:00.083500Z

(ns app.client
  (:require
    [com.fulcrologic.fulcro.application :as app]
    [com.fulcrologic.fulcro.components :as comp :refer [defsc]]
    [com.fulcrologic.fulcro.dom :as dom :refer [div ul li h3 label]]
    [com.fulcrologic.fulcro.algorithms.merge :as merge]
    [com.fulcrologic.fulcro.rendering.keyframe-render :as keyframe]
    [com.fulcrologic.fulcro.rendering.ident-optimized-render :as ident-optimized]
    [com.fulcrologic.fulcro.algorithms.denormalize :as fdn]
    [com.fulcrologic.fulcro.mutations :as m :refer [defmutation]]
    [com.fulcrologic.fulcro.algorithms.data-targeting :as targeting]))

(defsc Car [this {:car/keys [id model] :as props}]
  {:query [:car/id :car/model]
   :ident :car/id
   :initial-state {:car/id :param/id
                   :car/model :param/model}}
  (js/console.log "Render car" id)
  (dom/div
   "Model " model))

(def ui-car (comp/factory Car {:keyfn :car/id}))

(defsc Person [this {:person/keys [id name age cars] :as props}]
  {:query         [:person/id :person/name :person/age {:person/cars (comp/get-query Car)}]
   :ident         :person/id
   :initial-state {:person/id :param/id
                   :person/name :param/name
                   :person/age 20
                   :person/cars [{:id 40 :model "Leaf"}
                                 {:id 41 :model "Escort"}
                                 {:id 42 :model "Sienna"}]}}
  (js/console.log "Render person" id)
  (let [onClick (comp/get-state this :onClick)]
    (div :.ui.segment
         (div :.ui.form
              (div :.field
                   (label {:onClick onClick} "Name: ")
                   name)
              (div :.field
                   (label "Age: ")
                   age)
              (dom/button :.ui.button {:onClick (fn []
                                                  (comp/transact! this
                                                                  `[(make-older ~{:person/id id})]
                                                                  ))}
                          "Make Older")
              (h3 {} "Cars")
              (ul {}
                  (map ui-car cars))))))

(def ui-person (comp/factory Person {:keyfn :person/id}))

(defsc PersonList [this {:person-list/keys [people]}]
  {:query [{:person-list/people (comp/get-query Person)}]
   :ident (fn [] [:component/id ::person-list])
   :initial-state {:person-list/people [{:id 1 :name "Bob"}
                                        {:id 2 :name "Sally"}]}}
  (js/console.log "Render list")
  (div :.ui.segment
   (h3 :.ui.header "People")
   (dom/ul
    (map ui-person people))))

(def ui-person-list (comp/factory PersonList))

(defsc Root [this {:root/keys [list]}]
  {:query [{:root/list (comp/get-query PersonList)}]
   :initial-state {:root/list {}}}
  (js/console.log "Render root")
  (dom/div
   (dom/h3 "Application")
   (ui-person-list list)))

(defonce APP (app/fulcro-app {:optimized-render! ident-optimized/render!}))

(defmutation make-older [{:person/keys [id]}]
  (action [{:keys [state]}]
          (swap! state update-in [:person/id id :person/age] inc)))

(defn ^:export init []
  (app/mount! APP Root "app"))

(defn get-components-that-query-for-a-prop
  [prop]
  (reduce
   (fn [mounted-instances cls]
     (concat mounted-instances
             (comp/class->all APP (comp/registry-key->class cls))))
   []
   (comp/prop->classes APP prop)))

(comment
  (comp/transact! APP [(make-older {:person/id 1})])
  (get-components-that-query-for-a-prop :person/age)

  (def before (app/current-state APP))
  (def after (app/current-state APP))

  before
  after

  (map
    comp/get-ident
    (get-components-that-query-for-a-prop :person/name))

  (let [state           (app/current-state APP)
        component-query (comp/get-query Person)
        component-ident [:person/id 1]
        starting-entity (get-in state component-ident)]
    (fdn/db->tree component-query starting-entity state))

  )

yubrshen 2021-01-28T05:19:00.083600Z

The only difference may be is the version of Fulcro. I’m using

com.fulcrologic/fulcro {:mvn/version "3.4.14"}
What could be the issue? Thanks!

nivekuil 2021-01-28T06:54:19.084100Z

has anyone thought about per-element activity markers? e.g. click a link/button and it turns into a spinner while waiting for a corresponding load/mutation. I was thinking about hooking into the tx processing stuff, but that might be gross and run afoul of f: state -> ui

Thomas Moerman 2021-01-28T10:22:22.084800Z

I think there is a problem with eb/error-boundary and localized css

Thomas Moerman 2021-01-28T10:28:21.086800Z

the :.y class name is expanded in function of the error boundary component instead of the containing component with the {:css [[...]]} section

(mui/grid {:item true
           :xs   12
           :lg   6}
  (err/error-boundary
    (comp/fragment
      (div "bla")
      (styled-paper :.y {}
        (mui/typography :.p
          {:paragraph true
           :color     :textSecondary}
          
          (dom/i "TODO search, filter...")

          )))))

Thomas Moerman 2021-01-28T10:28:27.087200Z

does this make sense?

Jakub Holý 2021-01-28T13:34:11.088400Z

I am trying to answer this question: When do you need to define :initial-state? My answer so far: 1. When you want to make sure that the component has particular props before any data is loaded from the backend 2. When the component has no state of its own and only queries for global data using {url-book}#linkqueries[Link Queries] 3. When a child component has initial state (f.ex. dynamic routers do) 4. (perhaps) When the component is used as a target of a dynamic router Thoughts? 🙏

Aleksander Rendtslev 2021-01-28T13:36:27.089300Z

Super exciting guys! I’m so impressed with everything you’re putting together. Big shoutout and a thank you for all your amazing work https://www.fulcrologic.com/copilot

tws 2021-01-28T14:30:02.090100Z

are there any fulcro friendly tools for First Run Experience?

Jakub Holý 2021-01-28T14:44:56.090200Z

@tony.kay I guess the word "tool" is missing from ☝️ after "great" in: > We use them every day in production environments and find them to be a great for solving ?

Jakub Holý 2021-01-28T14:45:35.090400Z

What is First Run Experience? What kind of tools do you have on mind?

tws 2021-01-28T14:53:30.090600Z

e.g. an interactive overlay to walk you through how to use an interface for the first time. like graying out most of a UI, but highlighting one area with “Click here to manage your account”, then you click next and it will highlight the next thing. mini-tutorial for first-time users.

👍 2
tws 2021-01-28T14:54:21.090800Z

i thought i had seen some tooling for other web frameworks (like Rails) to manage this with ease but now I can’t seem to find any.

Jakub Holý 2021-01-28T14:57:01.091100Z

I have not heard about anything like that for Fulcro

tony.kay 2021-01-28T16:18:03.091500Z

The answer is very simple: When the query needs to reach a particular part of the UI graph from the first render frame of the app. The query has the shape of the UI, but it is really a standalone API: The first render is literally passed the props of (db->tree (get-query Root) state state). Link queries dangling off the “edge” of the real data are never fulfilled because db->tree stops evaluating the query when an edge isn’t found. There’s nothing else it can do. An edge could be to-one, or to-many…how is it supposed to “invent” the missing edges?

👀 1
tony.kay 2021-01-28T16:23:49.091700Z

:thumbsup:

Jakub Holý 2021-01-28T16:37:35.092500Z

I would appreciate help with my Fulcro tutorial. Is the text and diagram below clear? Useful? Can it be improved? > The image below shows how the query fragments of all components are composed into the Root component’s query and sent to the backend which responds with a tree of data, which is then propagated down from Root to its children and so on. (We omit the role of the client DB here for simplicity.)

Jakub Holý 2021-01-29T08:22:10.111Z

Mermaid looks cool but it is not powerful enough for this case (adding the query and data arrows to the UI tree,.. 😭

tony.kay 2021-01-29T17:21:22.125800Z

I have one other comment: The query/return of data is not tied to root. The query has a default target of a root key based on a subtree query, which can be re-targeted. This is not an insignificant point. You never really query from root.

Jakub Holý 2021-01-29T18:11:18.126500Z

Right, you don't do (df/load! :all-data Root) , you do something like (df/load! :all-projects Project) where Root shows a list of projects and has query like [.. {:all-projects (comp/get-query Project)}] . Is that what you are saying? Now I need to think hard how to make that clear in the figure without just confusing everybody... So the Root query is used to fetch data from the client DB and build props and send them down to Root but it is never used to load the data.

tony.kay 2021-01-29T18:46:49.126900Z

right….never is a strong word, but in real apps that is true

tony.kay 2021-01-29T18:47:34.127100Z

you’re always loading (or merging) some targeted subtree

tony.kay 2021-01-29T18:48:00.127300Z

so, I’d draw the SERVER query from some child node and merge back to that node

tony.kay 2021-01-29T18:48:18.127500Z

where that child might be dashed in some cases (doesn’t exist in graph yet)

tony.kay 2021-01-29T18:48:26.127700Z

the UI query is from root

tony.kay 2021-01-29T18:48:56.127900Z

so, if you change the graph to say “UI Client DB” on the right, then that diagram is absolutely right

tony.kay 2021-01-29T18:49:29.128100Z

but for merge-component and server interaction the in/out lines should connect to a sub-portion of the left graph, not the root

Jakub Holý 2021-01-29T19:25:04.129300Z

Genius! I will do just that, replace Pathom with Client DB

tony.kay 2021-01-30T00:44:43.133400Z

yeah, now all you need is the picture of the graph in the client db, and to the right of the client DB another query out, data in that targets a portion of the sub-graph.

Jakub Holý 2021-01-30T11:37:47.135300Z

Hm, I am tempted to leave it as is and not discuss that part :-) But we can give it a try. Any thoughts about how to visualize the graph in the client DB and the query<>data of a subtree?

nivekuil 2021-01-30T12:35:51.139800Z

maybe some inspiration from the diagrams here? https://tonsky.me/blog/the-web-after-tomorrow/

👀 1
yubrshen 2021-01-31T17:11:17.189300Z

@holyjak really appreciate your help for us to understand Fulcro! I wish to have some more narrative to explain the diagram in your tutorial. To the minimum, explanation of the legends, such as, the meaning of those icons of solid circle, square, and triangle, etc. (they look intriguing.) Another aspect, please explain the cause-effect relationships of those links in the graph, what happen first, what are the consequence of that action, etc. (I did check your tutorial, it's a pity that without more narrative, your wonder diagram might not reach its fuller impact.)

❤️ 1
Jakub Holý 2021-02-01T09:30:33.197300Z

Hi @yubrshen! Thank you. I have now added a simple legend to the figure. If you read the paragraph just above it, it explains the causal sequence. See https://github.com/fulcro-community/guides/blob/main/minimalist-fulcro-tutorial/index.adoc#components-query

dgb23 2021-01-28T16:47:55.094200Z

When looking at this I’m trying to picture an animation in my head. It’s certainly useful. An improvement could be a series of steps to kind of see how the tree is walked maybe.

Jakub Holý 2021-01-29T09:35:30.111200Z

Great idea, an animation would be awesome. Only I have no idea how to make it 😞

dgb23 2021-01-29T12:30:23.123100Z

An easy and low effort way is to draw the pictures in a series and leave them statically. Another possibility is to draw them as SVG use CSS transitions and a bit of JS to trigger them. If the latter is an option I’m happy to help!

Jakub Holý 2021-01-29T15:02:13.123300Z

I do not know how I would do that. But please give it a try, if you want, the SVG is here https://github.com/fulcro-community/guides/blob/main/minimalist-fulcro-tutorial/fulcro-ui-query-data.svg

dgb23 2021-01-29T15:11:07.123600Z

I’m in a hurry right now, but I could try next week. However a prerequisite would be to use github pages so you can include arbitrary CSS/JS. I don’t know if this is out of scope for this tutorial?

dgb23 2021-01-29T15:13:02.123800Z

Let me first send you a prototype next week when I have time and then we go from there!

👍 1
Jakub Holý 2021-01-29T15:13:02.124Z

No, I think it would be a good idea to create GH pages for the fulcro-community guides.

dgb23 2021-01-29T15:13:10.124200Z

cool!

Jakub Holý 2021-01-29T15:13:19.124500Z

thank you!

dgb23 2021-02-03T13:30:19.227200Z

Hey I just came up with a very simple idea that is easy to implement: would it be useful to convey the steps with simple highlighting? So each step highlights both a node in the structure (left) and a related part in the EQL literal (right)? I think that might already convey very much without too much effort!

dgb23 2021-02-03T13:30:51.227400Z

If you think this is a good idea I will make it and send it tomorrow.

Jakub Holý 2021-02-03T16:15:22.232400Z

If you are sure it is worth the effort, go for it!

👍 1
dgb23 2021-02-03T22:21:24.234100Z

dgb23 2021-02-03T22:21:32.234500Z

not sure if it was worth it 😄

dgb23 2021-02-03T22:22:41.234700Z

if you think it’s useful I can also improve it a bit. Idk!

dgb23 2021-02-03T22:23:58.234900Z

it’s not much work

Jakub Holý 2021-02-05T09:47:47.244900Z

I just realized that to be able to actually use it, we will need to move from using GitHub's .adoc preview to GitHub pages because I think the former does not support custom javascript 😢 . It was the plan anyway but I have very little time so it will take a while 😭

👍 1
henrik 2021-01-28T16:53:10.095300Z

I'm trying to refresh a parent from the context of a child, like this:

(comp/transact!! child-this `[(m/set-props ~{:child/value value})] {:compressible? true
                                                                    :refresh [[:parent/id parent-id]]})
The parent doesn't refresh (as verified by turning on "highlight updates" from react devtools). Am I using this correctly? Renderer is com.fulcrologic.fulcro.rendering.keyframe-render2.

henrik 2021-01-29T11:14:36.114500Z

transact!! just merges {:synchronous? true} into the opts of transact!, and being able to target refreshes is specifically interesting with synchronous transactions, since they only refresh the caller (by default).

henrik 2021-01-29T11:29:03.117600Z

Yeah, so refresh/`only-refresh` seems to be for non-syncronous transactions only. Since synchronous is recommended for value transactions to an input, I'm not sure how to make a parent aware of the input change interactively, when this is necessary.

henrik 2021-01-29T11:38:43.120900Z

Running comp/refresh-component! seems to be an inelegant, but effective, workaround.

nivekuil 2021-01-28T16:54:27.095500Z

I would appreciate the network lines being colored correspondingly, and the data line pointing to the root node directly. also not sure why there's 2 triangles or why some shapes are outlines and some are solid

👍 1
2021-01-28T17:02:02.095800Z

I love where you're going with this. I also didn't realize right away that the shapes of the component tree on the left correspond to their queries. Have you tried sketching out a version where each component is a box and their queries are yellow shapes inside them? Using shapes instead of keywords is great!

😻 1
2021-01-28T17:03:28.096Z

I suspect that would help make it clear that the white shapes don't have queries (if that's correct).

✅ 1
Jakub Holý 2021-01-28T17:07:38.096200Z

If I understand your response correctly then it conforms the 3 (4) points I wrote above. Right? In particular 2 shows where the UI cannot be reached without the help of initial data.

Jakub Holý 2021-01-28T17:23:57.096400Z

I do not really understand what "Query reaching a part of the UI" means. Query just is, it doesn't do anything. I guess you mean the process of fulfilling the query from the client DB reaching (and not skipping) a particular part of the query? I also do not comprehend "Query being an API". Sorry 😢

Björn Ebbinghaus 2021-01-28T17:44:29.096700Z

The keyword is: :only-refresh

tony.kay 2021-01-28T18:50:32.097100Z

Sorry, yeah, db-&gt;tree is the query API. It runs the query against the db. When it reaches a join that has no corresponding real data then it HAS to stop because there is no info on how to spring such an edge into existence. So, if there is a “link query” deeper in the query from there, it will not been realized, because there is no way to reach it. (sure, it comes from the root, but it’s dangling off in space in the reified graph, so it simply cannot make it’s way to the UI that destructures the result from root). So, that’s what I mean by “query reaching UI”. Your intention when you write a compoentn-local query is that the query processing will “reach the point and fulfill your request”, but if you didn’t connect the graph, then it cannot reach the query that is associated with that part of your UI.

👍 1
Chicão 2021-01-28T19:49:00.101700Z

Hi, I want connect fulcro +pathom and datomic, but I don't want use RAD template, if I change here <https://github.com/fulcrologic/fulcro-template/blob/master/src/main/app/server_components/pathom.clj#L54> with a datomic (d/db conn) my parsers from pathom will have access to datomic and will be able to make a pull query ? May someone has an template to share if mee I will be grateful.

JAtkins 2021-01-28T20:36:21.101800Z

Yes, this is great... My only issue is that the final map {triangle} has only one element. it won't compile 🙂

😂 1
JAtkins 2021-01-28T20:42:12.102Z

so, if the tree is

a -&gt; b -&gt; c
and the query is
[:a/id {:a/child [:b/id {:b/child [:c/id]}]}]
and (a,c) have initial state you care about, you must have initial state on b. otherwise your state will look like:
{:root/a 1
 :a/id {1 {:a/id 1 :a/child nil}}}
and db-&gt;tree halts, even though the query continues

👍 2
👀 1
JAtkins 2021-01-28T20:44:13.102400Z

I know that's what you said in #3, but this rule kind of encompass all of your 4 rules.

Jakub Holý 2021-01-28T22:03:56.102700Z

Thanks a lot for the explanation, Tony!

henrik 2021-01-28T22:26:53.105500Z

Same result. I understood :only-refresh as being the desired option when you want to override the default refresh (I.e., the calling component), rather than add to it. Is this a misunderstanding?

Jakub Holý 2021-01-28T22:29:34.106100Z

Thank you all, great suggestions!

2021-01-28T23:47:16.107Z

This copilot thing sounds like it has some overlap with Guardrails.

2021-01-29T12:09:28.122900Z

Interesting. I had been wondering if there is some potential for Guardrails and clj-kondo to be friends.

tony.kay 2021-01-29T19:01:29.128300Z

kondo is stricyly a static source analysis in a pre-compiled executable meant to give you Cursive-like feedback on your source in real-time. A dynamic analysis requires a running runtime using your code, and is much slower. So, it has different pros/cons. A static analysis can choose to tolerate syntax errors and other ills in order to give you structural feedback. It can also do some limited localized code comprehension to give you some help. Dynamic analysis needs your code to run in running order and has the same dynamic env drawbacks as REPL usage (load order, reloading dependencies, etc.). So, it is slower and more difficult to use; however, dynamic code analysis can give much broader coverage for finding regressions, cross-code behavioral problems, etc.

tony.kay 2021-01-29T19:02:13.128600Z

They are actually complimentary.

tony.kay 2021-01-29T19:04:58.128800Z

CLJ-kondo - static structural problems and some localized errors Guardrails - Real runtime data flow detecting problems against real use-cases (requires active usage) Copilot - Leverage generated data and “pretend” to run your code to detect the things that Guardrails MIGHT find if you were to run your project enough times. Can actually do a lot more than GR, but the “runtime” mode of GR is also complimentary. Unit Tests - Test specific cases and behaviors you want to prove right Generative tests - Exercise algorithms under some more specific external constraints/proofs. Integration Tests - Simulate real full-stack system use. Finds integration problems and bugs.

tony.kay 2021-01-29T19:05:08.129Z

IMO they all bring something of good value to the table. They just each have different pros/cons. More tools in the toolbox, so to speak.

2021-01-29T23:27:51.133200Z

Thanks for the detailed explanation @tony.kay.

2021-01-28T23:51:50.107100Z

Looks good, you are making some amazing contributions to the Fulcro community lately @holyjak. As a small suggestion, you may want to consider doing the diagrams in something like https://github.com/mermaid-js/mermaid as it lends itself better to version control and alleviates the need to think about styling as much.

👀 1
❤️ 1