Fork me on GitHub
#fulcro
<
2020-02-10
>
thosmos00:02:43

I think this has come up a few times, but I forget if there was a norm around it? I’m having the issue where when I try to reset my form, some fields that were missing when pristine was created don’t get reset. It’s the good ol “merge doesn’t delete keys” problem again. I updated the pristine->entity* fn to handle it. Does this seem like a reasonable fix? Is there a more efficient way of doing it? https://github.com/fulcrologic/fulcro/pull/370/commits/a11458012183049fb6a154caf13e121f9ca39519

tony.kay14:02:08

I think it probably just needs to get the set of form fields (from component-options or the config itself). That is the set of things that can be reset. 1. dissoc those, 2. copy the pristine back….I just missed (1).

tony.kay14:02:28

of course, it does need to follow subforms, but I think the code already does that

thosmos17:02:12

yeah I made another commit after the one above that has a super efficient merge replacement. not sure if it’s faster or better than the dissoc / assoc method, but probably faster. But it might be less clear.

tony.kay17:02:30

in this case faster is very much a premature optimization…how many fields are forms going to have? not even hundreds. The React refresh is going to be the majority of the time. Without measurements, it is possible that the constant-time overhead of going to-from transients could actually be slower too.

tony.kay17:02:52

so, to sacrifice clarity seems a poor choice here

thosmos20:02:34

yes I agree

thosmos20:02:17

Even though it’s irrelevant in this context, and mainly just to satisfy my own curiosity of how to run a benchmark on a fn, I did a quick benchmark of our three options: https://gist.github.com/thosmos/0033d81cccd1234a028fc0a583c04885 But none the less, I agree that the clarity of your code is far more important than 0.2µs

tony.kay20:02:09

Yes…and I was mainly saying that until you ran that benchmark it would have been impossible to know that there was any kind of performance “win” at all.

tony.kay20:02:55

and good learning experience: you should definitely know how to measure things well if you’re optimizing code.

tony.kay20:02:48

NOTE: You should be benchmarking in a CLJS REPL…JVM != JS VM 😉

thosmos20:02:29

hehe I know .. one step at a time

thosmos21:02:23

haha, so my curiosity was just too much … CLJS simple-benchmark

[], (pristine->entity-1 modified-state-map [:person/id 1]), 9000 runs, 39 msecs
[], (pristine->entity-2 modified-state-map [:person/id 1]), 9000 runs, 41 msecs
[], (pristine->entity-3 modified-state-map [:person/id 1]), 9000 runs, 47 msecs
https://gist.github.com/thosmos/6883c2a649742e036efaeababc3da63f

tony.kay21:02:51

so 5 microseconds 😜

tony.kay21:02:11

roughly my experience: JS runs about 2x slower

pithyless13:02:27

fulcro-rad-datomic has some logic to parse enumerated-values from simple keywords to qualified keywords. This logic is not duplicated in rad.attributes and so e.g. the semantic-ui/enumerated-field does not properly set options (uses the unqualified keyword). Should this logic be added to rad.attributes (e.g. attr/new-attribute could generate the canonical qualified-kw form)? Or should this be handled at the enumerated-field? https://github.com/fulcrologic/fulcro-rad-datomic/blob/e9a8759086d0dea17b45ceaa38bd9df5d26bfbdb/src/main/com/fulcrologic/rad/database_adapters/datomic.clj#L255-L267

tony.kay14:02:57

Good question. I was struggling with that myself, which is why it is still half-baked. Each data store handles those differently, so it does not make sense for the rendering layer to have to understand that. But, does that mean you should customize your attribute to your db layer (i.e. in Datomic use nsed kws, and in SQL use strings or something?), or should we use perhaps the Datomic convention as the standard and have the db adapters adapt it to the storage. I’m currently leaning in the direction of the latter, since adapters could always add their own translation config to the attribute itself (i.e. a map that says what the keywords should map to in storage)

tony.kay14:02:04

Also, which version are you using? I fixed that up a bunch in the at the end of last week. It should be working in the demos, but that doesn’t mean it is totally “fixed”

pithyless14:02:25

I'm in favor of using ns-kw convention everywhere (in the spirit of pathom and maximal graph), and a db adapter is in a better position to adapt to custom mapping if necessary. I'm currently testing with fulcro-rad "0.0.1-alpha-1", but not using fulcro-rad-datomic myself (I'm currently running with a custom backend implementation that is using the same conventions as fulcro-rad).

pithyless14:02:54

BTW, it has been pretty smooth sailing so far using a custom pathom/datomic backend so far, so that's a good sign that the pathom and delta-diff convention is a pretty good separation of concerns. 🙂

tony.kay15:02:49

Nice. Yes, that is definitely a desired outcome. Glad to hear it.

tony.kay15:02:18

Now I just need to get reports a little more refined in API…it’s still the first draft from my conference-driven development.

pithyless15:02:16

fulcro-rad/form does not seem to call fs/pristine->entity* on cancel. Is this a bug or should the cleanup be happening elsewhere?

tony.kay17:02:42

should be exiting the state machine, route, and fixing app state.

pithyless19:02:40

Don't think it makes sense to open a new issue at the moment, but I added a comment: https://github.com/fulcrologic/fulcro-rad/issues/10#issuecomment-584295880

tony.kay17:02:17

RAD book updated. I added more info about the built-in type support for date/time/zone and isomorphic arbitrary precision math/numerics.

tony.kay17:02:42

I’ve also got some ideas around adding support for arbitrary logic to forms (i.e. calculating line-item subtotals in an invoice form, which are calculated on the fly). Might be interesting to think about showing an example where Clara Rules engine was hooked in, and the “table entries” from the Fulcro database were automatically inserted as “facts”, run rules engine, then export the resulting changed entities back to the Fulcro db. Not sure it would perform well enough, but is a fun idea at least 🙂

👍 20
Piotr Roterski20:02:14

I've just read https://www.clara-rules.org/docs/approach/ and I have to say - a working example of Fulcro RAD integrated with Clara Rules would be a powerful.

tony.kay17:02:03

But, I think perhaps standardizing the base report API might need to come first.

tony.kay17:02:32

since that is quite important for getting something useful going….no autogenerated “master” screens for our “details” until that happens

Aleed20:02:01

@tony.kay what was the final take-away from your experiments with React hooks? (re: your Fulcro 3 Patreon post: https://www.patreon.com/posts/fulcro-3-25683469)

tony.kay20:02:06

Hooks are doable, as are “standalone” nested roots. I don’t personally need them, and see no clear advantage to them for Fulcro, since the model works very well historically and in practice. React is doing hooks to help solve issues that are inherent to their data model philosophy, which differs from Fulcro’s. That said, it would not, per se, hurt anything to support hooks. I have limited time, though, and no desire to implement something that I see as having no immediate benefit to me or my users. When someone comes up with a compelling use-case, I’ll consider it.

tony.kay20:02:54

I can think of idealized things that would be “sorta nice”, but they just aren’t what I need right now

Aleed20:02:04

Related question: seems like every ident/query is tied to a component, is that so Fulcro (rather than React) can control rendering? Or are there other benefits?

Aleed20:02:31

Question I’m asking relates to answer you have in section 3.6.1 of docs (“Why not have components just grab their data (sideband)?”) but I’m still not fully grasping benefit based on that answer. For example, Apollo Graphql takes the sideband approach, but you can interact with multiple remote sources and manage global state that way as well.

tony.kay20:02:45

Correct. The problem is when you compose more than one thing that is “grabbing data sideband” then you either have to introduce a caching layer that multiple components use (to share the data), a normalization db layer (which is what Fulcro does), or just deal with the fact the none of the crap is remotely in sync.

tony.kay20:02:19

In Fulcro, everything is normalized via the idents, so composition over time is trivial. Just use the correct ident definition on a component that uses some bit of data, and you’re automatically accessing the central cache, which is not some opaque library thing: it’s a simple-to-understand map.

Aleed20:02:49

can more than one component use the same ident? i.e. if you need to have different views of the same data

tony.kay20:02:07

in fact: they MUST

tony.kay20:02:12

or you’re doing it wrong

tony.kay20:02:41

That’s part of the power: you look at a component’s definition and you know: 1. Where it lives in the db 2. What it needs (from the query) 3. What it’s children are (which gives you runtime introspection) 4. How to normalize an incoming tree of data (combo of query and ident) 5. How to convert the db back into the desired tree of react props.

tony.kay20:02:55

Those are all critical bits of info, and you get them all from two simple declarative items co-located on the component itself. Instant visibility of everything you need to know, in as compact a form as possible. I hear ppl claim that “there’s boilerplate in Fulcro” when referring to query/ident…I completely disagree: this is useful and DRY info.

tony.kay20:02:39

Fulcro support for forms (and recursive composition of them), dynamic routing (and recursive composition of them) are two examples of where this shines.

tony.kay20:02:44

The UI State machine code works super well because of them. OVer and over again I find nice “wins” from these two simple data items. Fulcro 3's expansion of the options map to be completely “open” is making RAD easy to build and make extensible.

👍 4
Aleed20:02:18

reading above slowly to process it, but regarding routing/forms, that was a related question I wanted to ask are you expected to use Fulcros routing/form when using it?

Aleed20:02:51

two concerns I have is 1) for react native I'd rather use react navigation's routing since it has built in navigation components

Aleed20:02:52

2) not sure if Fulcros routing will supports nested routers (since it's global and not using React context?) so wonder if it's possible to implement a modal gallery with them

tony.kay20:02:47

The answer is “no”, you don’t have to use them. They are there because they work well and are a common need.

tony.kay20:02:10

but I think you’ll find that if Fulcro is “worth it” in terms of the other “wins”, then “can I use lib X” becomes less important. I suspect that your desire to use a 3rd parting routing lib will not work…not because you’re required to use what I’ve supplied, but because routing is a data modeling concern in Fulcro, and a 3rd party lib won’t “get” that.

tony.kay20:02:43

That said: you’ve just pointed out an example where “independent Fulcro Roots” would help someone: It would make it easy to use a 3rd party routing lib.

tony.kay20:02:39

For me, I’d rather have my “routes” be part of my state, so it doesn’t help me…but it is a reasonable argument around letting Fulcro break out of the tree model.

tony.kay20:02:06

Fulcro is general purpose, so (2) is: of course you can make a modal gallery. But I’d also say that that has little to do with “routing” unless routing is the implementation detail you’ve chosen to implement it. One is an application feature, the other is one way of implementing it….but I did just say that Fulcro’s routers nest.

tony.kay20:02:45

That’s all the time I have for this line of questions today…Please see book/videos for more info, or perhaps others can chime in 🙂

Aleed20:02:53

thanks for the helpful responses 🙌

thosmos20:02:54

There’s probably a better way of doing this, but I wanted to co-locate my form validation messages with the validation logic, and also have multiple validations and messages per form field: https://gist.github.com/thosmos/95cdcb5480c9144f7b29d97d5475238b

kwladyka21:02:23

hey, maybe my solution will inspire you or you can just use it instead. I don’t use fulcro, so hard to say how this fits into your needs. Anyway I will be interested in to hear feedback from you. https://github.com/kwladyka/form-validator-cljs https://clojure.wladyka.eu/posts/form-validation/

thosmos21:02:10

nice! I found that after I’d made my quick thing, and yes, I’m thinking about using or adapting yours!

kwladyka22:02:52

happy to hear ❤️

otwieracz21:02:31

Is this possible, that dom/options is somehow different from options ?

otwieracz21:02:24

Where it should be option

otwieracz21:02:48

Is there any way to generate non-wrapped DOM element?

tony.kay21:02:01

call create-element on “input”

tony.kay21:02:07

make that a function yourself

tony.kay21:02:24

but non-wrapped inputs in React only work right with component-local state

tony.kay21:02:31

well-known React limitation/issue

tony.kay21:02:43

But to your specific question about the DOM option tag…that may in fact be a bug no one has noticed (I never use raw select like that)

tony.kay21:02:00

actually, there is probably no reason select and option should be wrapped. The misbehaviors mostly affect things like focus/selection/cursor jumps.

tony.kay21:02:44

I’m loath to touch those, since the likihood they are in use is high

tony.kay21:02:52

unless we can verify a real bug

tony.kay21:02:23

@slawek098 Try using (dom/create-element "option" #js { … })

tony.kay21:02:45

props must be a js object in that call.

tony.kay21:02:18

sorry…that should be dom/create-element

Jakub Holý (HolyJak)22:02:51

Hello! I struggle to understand how to implement a Person component that contains children showing different views of the person. This is a tree but the data is flat, all of these need a person. How will the queries of the children be included in the parent? `(defsc Person [_ {:person/keys [name] :as person}] {:query [:person/id :person/name {??? (get-query AgeView)],..} (div name (ui-age-view person #orWhat?))) (defsc AgeView [_ {:person/keys [age]}] {:query [:person/age],..} (p age))` What is the correct Fulcro approach, when nested components need subsets of the same single entity? How to write the join query on the parent component? Normally I have eg a Father entity with a :father/child [:child/id 123] attribute and join on that, {:father/id.. {:father/child (get-query Child)}] but here is no such thing... (This is a simplified and thus somewhat stupid example but comes from a real need.)

thosmos22:02:02

You could just do like this

thosmos22:02:06

(defsc AgeView [_ {:person/keys [age]}]
  {:query [:person/age],..}
  (p age))
(def ui-age-view (comp/factory AgeView))
(defsc Person [_ {:person/keys [name] :as person}]
  {:query [:person/id :person/name :person/age]}
  (div name (ui-age-view person)))

Jakub Holý (HolyJak)22:02:45

Right. But then I need to keep track of what data each *View component needs and ask for them all in Person.

thosmos22:02:31

ok, well but you need to give that join to AgeView a special key anyway

thosmos22:02:53

maybe :ageview

thosmos22:02:35

because the :person/age is just a basic data type, not a reference

thosmos22:02:59

but you might be interested in looking into union queries

👀 4
thosmos22:02:04

but as for your initial question, if you use [:name {:foo (comp/get-query FooComp)}] you’re saying that the data structure is {:name "name" :foo {:foo/one "one"}}

thosmos22:02:29

or use set-query to update the query with new keys before running a load, etc

👀 4
thosmos22:02:59

or use several different components for different views

thosmos22:02:54

or use a lambda form of :query and return different keys for different props

thosmos22:02:08

you might not be finding THE way because there are many ways

Jakub Holý (HolyJak)22:02:22

So I can use whatever key I want in the join, it doesn't need to match an attribute on the "current entity"?! So if Fulcro doesn't find a :foo attribute on the current entity then it will simply resolve the right side of the "join" against the same current entity, ie person? It reads somewhere in the book that Fulcro walks the query and DB in parallel and stops if it runs into a nil. Won't this happen when it sees the :foo property in the query and it has no such property in the current DB entity (a person)?! I feel confused...

Jakub Holý (HolyJak)22:02:56

I wanted to understand this simpler case before proceeding to my actual use case where I don't want to show all the subviews at once but always just a single one so I wrap them in a Router. In that case there is no explicit call to ui-age-view so I was unsure how to propagate the current person to the subviews (eg AgeView) sitting under a Router...

tony.kay23:02:16

Ah, I just read this part…you want a router in the middle (which there is no need for I don’t think…why not just use a local property and choose which functional sub-tab to “render”)…but anyway, the way to pass the current local props through a router to a component is through computed. The children themselves would not query for person stuff, since you don’t want the router in the query at all.

tony.kay23:02:10

so, you’d elide the router itself from the server query using the load option :without on the router join key from the Person layer.

tony.kay23:02:21

and then you could just augment props and pass them down through the router as computed, which will look a little weird, but is perfectly fine:

(defsc A [this props person-from-above] ...)

(defsc SomeRouter [this props]
  {:router-targets [A B C]})

(def ui-some-router (computed-factory SomeRouter))

(defsc Person [this props]
  {:query [... {:router (get-query SomeRouter)}]
   :ident :person/id}
  (ui-some-router router props))

...

(df/load! app [:person/id 22] Person {:without #{:router}})

❤️ 4
tony.kay23:02:53

then A/B/C would see the original person props in their computed param.

tony.kay23:02:32

but again, I probably would not model it that way, I’d do something much simple like:

(defsc Person [this {:ui/keys [page]}]
  {:query [:ui/page :person/id ...]
   :ident :person/id 
   :initial-state {:ui/page :first}}
  (case page 
    :first (render-page-1 props)
    ...)))

tony.kay23:02:02

then loading is clean, etc. There’s no need to nest all that mess in stateful components, since the only bit of real state you have is “which page are you on?”

tony.kay23:02:22

If it has much in the way of logic, I’d recommend a UI state machine.

Jakub Holý (HolyJak)15:02:03

Thanks a lot, Tony! The reason I thought a router might be a good match is because I am actually implementing a stepwise process - a "wizard" - that the user goes through and I want the current step reflected in the URL so that the user can go back/forward. I have something like (pseudocode):

(defsc Step1 [_ _ computed-person]
    (dom/p (str "Hello" (:name computed-person) "! Click here to start:")
           (dom/button {:onClick #(dr/change-route app "step2")} "start")))
(defrouter WizardRouter [..] {:router-targets [Step1 Step2 Step3]})

(defsc Person ... (ui-wizard-router))
(defsc MissingPersonId [...] (dom/p "Missing user id from the URL!"))

(defrouter TopRouter [..] {:router-targets [MissingPersonId Person]})
Is this an inappropriate use of routers?

tony.kay15:02:00

If you’re going to do html5 style routing, then it seems appropriate

👍 4
Jakub Holý (HolyJak)16:02:56

Thank you very much! I can imagine how precious your (and others') time is and I really appreciate the help and advice.

tony.kay16:02:04

well, always hoping ppl with make blog posts and demo apps about the thing they were trying to do.

tony.kay23:02:59

@holyjak The thing you’re looking for is Pathom placeholders (if you’re wanting the server to resolve things this way). If you just want to have subcomponents that pick apart props, you can just use non-stateful components or functions…no need for everything to have a query.

tony.kay23:02:19

Placeholders allow you to “fan out” an entity along virtual edges

tony.kay23:02:21

(defsc Person [this props]
  {:query [:person/id :person/name '*]
   :ident :person/id}
  ... use normal functions to render bits ...)
is the “don’t have nested queries” option, and placeholders looks more like this:
(defsc PersonAge [this props]
  {:ident :person/id 
   :query [:person/age]} ...)

(defsc Person [this {:keys [>/person-age]}]
  {:ident :person/id
   :query [:person/id {:>/person-age (comp/get-query PersonAge)}]
  (ui-person-age person-age) ...)

fulcro 4
tony.kay23:02:04

See Pathom docs for placeholders.

tony.kay23:02:48

This is what we’re talking about in talks like “The Maximal Graph” and “Why Use Fulcro?” when we say you can reshape the graph to fit the UI. Your server has a fixed shape, and in your case you want to turn a node into a graph…which is totally possible because we’re not tied to static GQL schema 🙂