This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-01-28
Channels
- # aleph (10)
- # announcements (1)
- # aws-lambda (1)
- # beginners (162)
- # calva (81)
- # chlorine-clover (2)
- # cider (18)
- # clj-kondo (2)
- # cljs-dev (1)
- # cljsrn (12)
- # clojure (64)
- # clojure-australia (6)
- # clojure-europe (13)
- # clojure-nl (3)
- # clojure-sweden (26)
- # clojure-uk (36)
- # clojurescript (45)
- # community-development (10)
- # conjure (16)
- # core-logic (4)
- # cursive (6)
- # datascript (1)
- # emacs (1)
- # events (2)
- # fulcro (87)
- # girouette (5)
- # honeysql (4)
- # hoplon (3)
- # hugsql (3)
- # leiningen (8)
- # malli (18)
- # off-topic (33)
- # pathom (14)
- # reitit (5)
- # remote-jobs (1)
- # reveal (1)
- # shadow-cljs (50)
- # sql (3)
- # startup-in-a-month (1)
- # vim (5)
- # xtdb (30)
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.
(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))
)
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!Once I reached the end of Part 5, after adding the display of the count for over 30, and :refresh, then the behavior with clicking on the button of "Make Older" is the same as shown in the video.
In 3.4 the default renderer was changed from the ident-optimized one to the multi-root one which is not ident optimized and always renders from root, i.e. checks the props of all ancestors of an affected component for changes and renders them if changed. Most of the time the decreased complexity is worth the negligible performance cost of that.
Once I reached the end of Part 5, after adding the display of the count for over 30, and :refresh, then the behavior with clicking on the button of "Make Older" is the same as shown in the video.
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
I think there is a problem with eb/error-boundary
and localized css
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...")
)))))
does this make sense?
In 3.4 the default renderer was changed from the ident-optimized one to the multi-root one which is not ident optimized and always renders from root, i.e. checks the props of all ancestors of an affected component for changes and renders them if changed. Most of the time the decreased complexity is worth the negligible performance cost of that.
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? 🙏
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?
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.
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 😢
Sorry, yeah, db->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.
so, if the tree is
a -> b -> 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->tree
halts, even though the query continuesI know that's what you said in #3, but this rule kind of encompass all of your 4 rules.
Thanks a lot for the explanation, Tony!
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
@U0CKQ19AQ 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 ?
What is First Run Experience? What kind of tools do you have on mind?
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.
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.
I have not heard about anything like that for Fulcro
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.)
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
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!
I suspect that would help make it clear that the white shapes don't have queries (if that's correct).
Yes, this is great... My only issue is that the final map {triangle} has only one element. it won't compile 🙂
Thank you all, great suggestions!
Looks good, you are making some amazing contributions to the Fulcro community lately @U0522TWDA. 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.
Mermaid looks cool but it is not powerful enough for this case (adding the query and data arrows to the UI tree,.. 😭
I have updated the figure as suggested. Any further comments? Thank you all!
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.
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.
so, if you change the graph to say “UI Client DB” on the right, then that diagram is absolutely right
but for merge-component and server interaction the in/out lines should connect to a sub-portion of the left graph, not the root
Genius! I will do just that, replace Pathom with Client DB
updated img here https://github.com/fulcro-community/guides/blob/main/minimalist-fulcro-tutorial/index.adoc#components-query
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.
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?
maybe some inspiration from the diagrams here? https://tonsky.me/blog/the-web-after-tomorrow/
@U0522TWDA 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.)
Hi @U2QGRCMSM! 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
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.
Great idea, an animation would be awesome. Only I have no idea how to make it 😞
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!
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
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?
Let me first send you a prototype next week when I have time and then we go from there!
No, I think it would be a good idea to create GH pages for the fulcro-community guides.
thank you!
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!
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 😭
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
.The keyword is: :only-refresh
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?
transact!! is special and only refreshes the this
component I believe. Cannot you use transact!
?
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).
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.
Hi, I want connect fulcro +pathom and datomic, but I don't want use RAD template, if I change here
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.
start from the RAD template and delete all the RAD stuff. Inline the code that creates app
from RAD. That is likely to work.
i believe it's meant to be the pro paid version of guardrails
similar to how intelliJ has a free lite version and a pro paid version
You code in Guardrails. Copilot uses the same guardrails info to then do code analysis while you’re editing (instead of needing you to run the code)
So, copilot is an add-on tool with IntelliJ integration that is sorta like clj-kondo, but understands the data better, so it can find more interesting errors.
Interesting. I had been wondering if there is some potential for Guardrails and clj-kondo to be friends.
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.
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.
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.
Thanks for the detailed explanation @U0CKQ19AQ.
I have updated the figure as suggested. Any further comments? Thank you all!