Fork me on GitHub
#fulcro
<
2020-12-14
>
genekim00:12:58

In Fulcro RAD, is there an easy way to display in a form a string in a multiple line textbox / text area? Here’s how it currently appears in a form, and you can see what it looks like in the report (multiple paragraphs). Thank you!

tony.kay01:12:08

I seem to remember making a :textarea style…look in semantic-ui-controls, it has the map from styles to controls that are pre-installed…or add your own.

genekim01:12:35

Very cool — will look!!! Thx!!!

genekim00:12:31

For those of you interested in trying out Fulcro RAD, here’s a checklist I’m starting to build so I can create resolvers, reports and forms more quickly. The first time around took nearly 14 hours, with much help needed from @holyjak and @tony.kay (which you may have witnessed. 🙂 I managed to write an entirely new set of resolvers, a report and form in about two hours today. 🎉🎉🎉. This time around, it’s definitely feeling like the best of Ruby on Rails, where you can build a surprisingly large amount of stuff w/surprisingly little effort. Here’s that cookbook in a Google Doc — feel free to comment. I’ll clean up the doc, and keep recording what mistakes I make. https://docs.google.com/document/d/1tp3-Fgo0GRTnh11KPr7O-k15g8DiRekqGUDA6WtQqyk/edit# (I’ll make it publicly readable, and public can comment)

🎉 12
❤️ 6
mruzekw02:12:24

How does Fulcro do with queuing and retrying for when applications are offline? Also, Is there any support for cache persistence into something like LocalStorage or AsyncStorage?

Jakub Holý14:12:34

In addition to the tenacious-remote ns there is also https://github.com/fulcrologic/fulcro/blob/develop/src/main/com/fulcrologic/fulcro/offline/load_cache.cljc > Support for a loading system that supports keeping previously-seen query responses in a persistent cache. It supports the following features: Make a failed load return the last cached value. Support for a default value for the load in case you are offline and nothing is in the cache. Support for an eager/fast return from cache even when online. Indicating if the actual return should just go to cache, or both cache and app merge. and https://github.com/fulcrologic/fulcro/blob/develop/src/main/com/fulcrologic/fulcro/offline/durable_mutations.cljc

tony.kay03:12:39

Fulcro 3 has some additions along these lines. See tenacious remote

tony.kay03:12:49

(and the network of destiny 😉 )

tony.kay03:12:25

has to be idempotent mutation, and you don’t get reads of the written result until back online. Doing more is a harder task

stuartrexking11:12:49

I’ve seen the term “chrome” used to describe a top element in a component graph. Where does this originally come from? As an example, the template has TopChome defsc https://github.com/fulcrologic/fulcro-template/blob/master/src/main/app/ui/root.cljs#L198

Jakub Holý14:12:21

https://en.wikipedia.org/wiki/Graphical_user_interface#User_interface_and_interaction_design (2012) > The visible graphical interface features of an application are sometimes referred to as chrome or GUI and from the referred https://www.nngroup.com/articles/browser-and-gui-chrome/ > Chrome is the visual design elements that give users information about or commands to operate on the screen's content (as opposed to being part of that content). These design elements are provided by the underlying system — whether it be an operating system, a website, or an application — and surround the user's data. > ... > I don't know who came up with the term "chrome," but it was likely a visual analogy with the use of metal chrome on big American cars during the 1950s

tony.kay16:12:46

Plus one on the car analogy

stuartrexking04:12:09

🙏:skin-tone-2:

Jakub Holý16:12:43

Hi @tony.kay! Is it so that reports to not run (= fetch & show their data) if they are displayed as the default route of a router, i.e. (in URL terms) when I go to /myrouter instead of /myrouter/report-target-1 ? I believe you mentioned something about some UISM events not being triggered for default routes, causing this. Is this intentional or a bug? If it is intentional - what is the recommended solution to avoid the problem? My use case is that I have nested routers like this:

Root Router
 |- OrgList report
 \- OrgDetails component - ExtraInfoRouter -- Extra1 report
                                           |- Extra2 report
                                           \- Extra3 report
When a user selects a particular organization from the OrgList report, I route to OrgDetails , which contains a nested router with 3 reports. Because I only route to OrgDetails, the router will show the default (first) report, which will be empty / "loading" forever, due to the described problem. How to solve? 1. A workaround I have tried is to check the route in the nested ExtraInfoRouter and route explicitly to the 1st child if it does not contain any of its targets but you said that it is wrong to change the route during routing and I suspect it is indeed causing problems (routing to a Extra3 based on the URL does not work, it will show up empty, because the DB is messed up). 2. I could modify the OrgList row action to route to OrgDetails - Extra1 instead of just Org.Details but then OrgList is forced to know about the internal structure of OrgDetails, which seems bad. 🙏 Thank you for any tips!

tony.kay16:12:32

It is intentional. The first frame is a render of initial state…remember in Fulcro V === F(state)

tony.kay16:12:35

so, you need to start the routers… the dr/initialize will start all routers that can be found in the current query…however, it is still a good idea to do a route-to to the “default route” on startup to ensure the proper events have been sent to the proper router(s)

tony.kay16:12:37

The init function in RAD demo client is pretty much what I’d suggest for most apps:

(defn init []
  (log/merge-config! {:output-fn prefix-output-fn
                      :appenders {:console (console-appender)}})
  (log/info "Starting App")
  ;; default time zone (should be changed at login for given user)
  (datetime/set-timezone! "America/Los_Angeles")
  ;; Avoid startup async timing issues by pre-initializing things before mount
  (app/set-root! app Root {:initialize-state? true})
  (dr/initialize! app)
  (setup-RAD app)
  (dr/change-route! app ["landing-page"])
  (history/install-route-history! app (html5-history))
  (auth/start! app [LoginForm] {:after-session-check `fix-route})
  (app/mount! app Root "app" {:initialize-state? false}))

tony.kay16:12:05

doing the set-root! before mount allows you to do most API calls “headless”

tony.kay16:12:51

(that does not guarantee they are complete by mount, so you still need to be careful there), but it should at least ensure you’ve got your initial state in good shape. The fix-route mutation 2nd to last there is doing 2 things: Asking the auth state machine to check if you already have a session, and afterwards run the mutation `fix-route, which can change the route to what the user originally pasted into the URL. I didn’t add it to the demo, but having a top-level :ui/ready? flag that defaults to false is a nice add. Show an app loading animation while that is false, and then have the final fix-route (which will be in the tx queue in order) turn “on” the UI is a good touch for production

tony.kay16:12:04

(i.e. ensuring your app has stabilized before showing the first frame)

tony.kay16:12:55

^^^ Please feel free to send a PR on developer guide with this info if you see a good spot for it….I don’t have time to edit that into good shape, but it’s good info

Jakub Holý16:12:15

Thank you very much! OK, so the default route does not get the report init event (whatever it is called in practice) triggered because you want to be able to render the "first frame" when we assume that the whole system does not need to be ready yet. So best practice is to always route to a concrete target and leave the "fallback to the first target default" only for the initial frame render, correct? In my case that means that OrgList needs to know not simply its sibling target, the OrgDetails , but it must know the leaf target in its substree, i.e. Extra1 and that coupling is unavoidable. Correct? (And yes, I will think about how to best add the info to the Guide once I fully understand it.)

wilkerlucio19:12:12

hello, trying to use a RAD form here, it works for most fields, but when I add a field that's an :enum, the whole form stops rendering, and there is no error message, where is the next place to try to find the problem?

Jakub Holý19:12:05

No idea. Enum of what type? Is there an input control for that type? But normally RAD would warn if it was missing a control...

Jakub Holý19:12:39

You could define a custom (possibly dummy) control for the field to discover whether that is the problem. Or/and wrap the default with react error boundary...

JAtkins20:12:41

Turns out ao/enumerated-labels just needs to be a map of enum -> str, and it can't be a fn.

☝️ 3
❤️ 3
JAtkins20:12:19

Not sure yet why fulcro rad eats that error though. I would expect something in the log

Jakub Holý23:12:26

yes , that is a good question.

vsined19:12:36

I'm working on a multi-tenant Saas application, where each tenant company has a separate SQL database, and there are potentially thousands of tenants. Are Fulcro and Pathom suitable for this scenario?

Jakub Holý19:12:56

I'd believe so. Simply switch the data source in pathom's env for each request based on tenant ID using a pathom env middleware

vsined19:12:27

Thanks for the very quick reply!

👍 3
Jakub Holý22:12:49

Oh, I am re-reading https://book.fulcrologic.com/#_partial_routes : > We only trigger routing events on the *specific routers and targets that are included in the destination path > [..] however, it also means that it is easy for you to confuse yourself by not routing to a real "leaf". so that clears my confusion about routing. I just need to find a good way to find out the "leaf".

Jakub Holý22:12:36

@tony.kay I am still trying to wrap my head around partial routes. The book states that the user should always route explicitly to a leaf target, explaining that it is e.g. to avoid any unexpected, not-asked-for I/O along the path. So far so good. understand that until you explicitly route to a router, it is in the "unrouted" state. So why do we have the concept of a default target, as mentioned in defrouter docstring? When/why is it used? Why not simply display the body of the router until explicitly routed to a particular child target? If I understand it correctly, if I change-route! with a partial route, the target router will display its first target but no routing events will be triggered for the target (or any nested routers underneath) so it will be in a kind of a weird state, no? Question 2: It seems to me to create unnecessary coupling when the User[n] target from 17.1. Routers needs to know not only about its sibling Settingsbut also about all its nested routers so that it can correctly route to [settings "pane1"] when it in fact does not care about the content of Settings and would prefer for Settings to handle its nested routers itself. If I refactor Settings - adding a new router level or removing Pane1 - I have to remember to update all places that route to Settings accordingly. It could be solved if there was a "hook" where settings could trigger (change-route-relative! this this ["pane1"]) if the incoming route does not specify any of its children. But is there such a place? Or is the idea worse then coupling User to the internals of Settings?

tony.kay22:12:24

@holyjak close: 1. You route to targets which are children of routers 2. If you don’t mention a target in your path, then the router that controls that target will not get called, and yes, it will be in an unrouted state 3. The “default route” is about initial fulcro app state. It won’t even render anything at all unless that is set up…and the initial state a router returns is the intial state of all sub-targets, but the “current” route initial state will be set to the first of the targets…BUT that does not start the state machine, so the router technically does not know anything, and since routers are composable, they cannot assume they are even on screeen. Thus the need for you to tell the system when started what route you want.

👍 3
tony.kay22:12:29

The “current route” of an application is global knowledge that you have to control…it typically comes from the URL, and isn’t even fully under your control (user could press back). Don’t confuse that fact with the needs of “general composition” in code.

Jakub Holý22:12:12

Right, excuse my inexact terminology, I will try to be more rigorous next time.

tony.kay22:12:10

In terms of relative routes: You simply have to know the destination from anywhere you issue a new route…In terms of making a “default place to go if unspecified”…well, I have not written that feature

tony.kay22:12:32

I guess you could make Settings a target instead of a router

Jakub Holý22:12:45

Thank you! So the only raison d'etre of the default target is so that the app can get a meaningful state. That makes sense.

tony.kay22:12:06

and then nest another router within it, so then on enter Settings could optionally issue a relative change route if it felt the need

tony.kay22:12:19

you’ve got it, yes

Jakub Holý22:12:20

> on enter Settings could optionally issue a relative change route where would that be?

tony.kay22:12:47

:will-enter route deferred

tony.kay22:12:26

I don’t remember if there’s any restriction on a router also being a target…I cannot think of one.. It might not work…I don’t remember if the route scanning logic would like that. I think they have to be separate for the moment (routers cannot be targets), but it probably would not be hard to remove such a restriction

Jakub Holý22:12:28

ok, so I can route from inside that one, great to know! Thank you so muc!

tony.kay22:12:51

yeah, asking to route just issues a new transaction on the queue, which will run “later”

tony.kay22:12:05

up to you to prevent infinite txn loops 🙂

Jakub Holý22:12:52

I just remember that you told them that triggering change-route! inside defrouter 's body (when state is neither :pending nor :failed => not routed yet) is bad and confuses the system.

tony.kay00:12:34

defrouter’s body is optional, is a render, and should not side-effect…so confused what you mean

tony.kay00:12:08

perhaps book has confusing text?

Jakub Holý08:12:14

I mean that in my code I have

(defrouter DetailsDisplayRouter [this {:keys [current-state] :as props}]
  {:router-targets [LatestBillRunList SubscriberList]}
  (case current-state
    :pending (dom/p "Loading...")
    :failed (dom/p "Failed")
    (do ;; :initial / nil: no route selected yet, route to the latest bill runs & show nothing in the meantime
      (when props (routing/route-to! this LatestBillRunList (comp/get-computed this)))
      nil)))
as a workaround so that a component can route to its sibling without knowing about its nested routers, but as you say it is side-effecting and wrong

tony.kay16:12:41

yeah, that is about as wrong as you can do something 🙂

😂 3
tony.kay16:12:52

remember that defrouter is just a macro wrapping defsc…so you can use all component options there except the ones it uses (query/ident/initial-state)…I don’t think you can make it a hooks component, but you could glom onto componentDidUpdate.

👍 3
tony.kay16:12:39

just read the macro source for that it uses, and you can play with using anything it doesn’t

Jakub Holý21:12:12

Thank you, I haven't thought of that!

tony.kay22:12:33

Also note the new stuff where you can route to a target by class name, that is super handy. I never use strings anymore

👍 3
Jakub Holý23:12:45

(I am done with the flood of PRs now. But they are mostly super tiny so I hope it is not too much of a bother 🙏 .)

Jakub Holý23:12:53

I must remember to re-read the Fulcro Developer Guide regularly, it is getting better constantly :-)