Fork me on GitHub
#fulcro
<
2020-08-13
>
cjmurphy00:08:47

I have a defsc-report that uses a ::report/source-attribute that activates the remote resolver. What's the recommended way to make this optimistic? i.e. the data is already in the app db so fetching from the server is not at all necessary.

cjmurphy02:08:37

The answer is to have a local remote. But my inclination would rather be to not use RAD, use a conventional defsc. Just wondering out loud if it would make sense for a defsc-report to be able to have a :query, as an alternative to a ::report/source-attribute ??

cjmurphy02:08:15

I can see ::report/query-inclusions but don't see how it could be of much help.

tony.kay03:08:43

there is an override for ::report/machine (undocumented) for coding customizations to how things behave. This is the recommended way to do any behavioral customizations. I plan on adding a global override so you can make it application-wide, but have not yet. This would also answer your other question. Then you’d just derive your own state machine from the supplied one.

tony.kay03:08:07

ideally, each of my pre-supplied handlers would be in functions that you could easily re-compose into your own. Another option is “hooks” for everything, but that leads to option explosion, and I’d rather avoid that

cjmurphy05:08:44

I can see that ::report/load-report! is called from the UISM. So a new state machine could call its own function that would do a mutation to get the app state as required by RAD reports.

cjmurphy05:08:25

::report/source-attribute could then be ignored. However you would have to put something in there because it is a required attribute.

tony.kay14:08:02

So that’s true, but a minor issue with an easy workaround.

tony.kay14:08:17

making a macro’s error checking pluggable is a bit of a nightmare 😜

cjmurphy23:08:48

Overriding the state machine won't help for the other question. That is because the function ::report/report-will-enter is called directly from the macro-generated defsc's `:will-enter`. So, IMHO, a developer being able to put their own :will-enter still stands as a reasonable enhancement request.

tony.kay11:08:05

Ah, right, it has to return the right value. You’re right…send a PR

tony.kay11:08:18

user-supplied will-enter is allowed

tony.kay12:08:28

I’m in the source this morning…I’ll just do it

tony.kay12:08:27

On 1.0.0-RC2-SNAPSHOT @U0D5RN0S1

cjmurphy12:08:56

Unfortunately taking data from app state rather than using a resolver proved a bit more difficult, and maybe not what RAD is intended for (not yet anyway). Surely all that is required is a change to ::report/load-report! . I tried commenting out the load and doing

(uism/trigger! app (uism/asm-id env) :event/loaded)
just before changes to env. That kept the state machine working whilst it skipped the load. But that wasn't enough. More to it than I could comprehend...

tony.kay12:08:35

remember that RAD report exists because there is a lifecycle around the data, controls, and so on. It is designed to be a full-stack thing, and I had not considered you might do reporting from client-side data.

tony.kay12:08:41

so yeah, that might require some design work

tony.kay12:08:18

but I’m not sure why you can’t comprehend it…it’s a pretty small state machine, and the macro outputs a standard defsc. Perhaps you mean the rendering is hard to follow? That I would agree with. Maybe I’ll make another video that goes over some of the internals for customizations.

tony.kay12:08:34

oh…reports do have stages.

tony.kay12:08:27

so there’s that: Data gets loaded into one spot, then transformed (optionally) by custom function you supply. Then it gets filtered into a cache, then sorted into another cache, then paginated into the final display rows.

tony.kay12:08:57

those stages are an optimization for large reports. If you change sort order there is no need to re-run filter, so it can start part-way through the chain

tony.kay12:08:23

same with pagination: it’s a very fast constant-time subvec of the final display rows…no need to do filter/sort

cjmurphy05:08:49

Can comprehend now. Maybe unfamiliarity with state machines, or needed to sleep on it! Anyway I've commented out everything happening when :state/loading gets :event/loaded , and can see the remote data being loaded into :ui/loaded-data (or :raw-rows). For local population I will try to use BodyItem as the alternative source.

cjmurphy02:08:33

I have a need to override :will-enter on a defsc-report. This would be a simple change to the macro. My use case is that somewhere in the app state I have a 'current tab', that is used to highlight the currently selected tab. I want to start adding reports and forms to an existing application, but don't want to give up this highlighting feature.

cjmurphy04:08:18

I don't see how a different UISM can help here. ::report/report-will-enter is called directly from the macro-generated defsc's :will-enter.

nivekuil04:08:50

I'm under the impression that loading inside :will-enter ... route-deferred to replace one component with another at the same path is a mistake, since it always seems to append the new thing for a fraction of a second before the route fully materializes. am I wrong or is this just a case where dynamic routing is a no-go?

nivekuil04:08:16

I'm not sure what I can do differently with a custom UISM that avoids this problem the dynamic routers have

nivekuil04:08:20

For example, this is the transaction log from pressing back, routing to an already-loaded component. My ensure-loaded mutation just sends the target-ready callback when it hits cache, and what I'm seeing there is the list items bounce around, scrollbar getting smaller then larger

21:37:02:734
(com.fulcrologic.fulcro.algorithms.indexing/reindex)
21:37:02:649
State Machine(:app.ui/RootRouter) :ready! 
{}
21:37:02:603
(com.fulcrologic.fulcro.routing.dynamic-routing/target-ready
 {:target [:view/id #uuid "ccb73fea-46a6-51ce-8e66-0fec960f45ef"]})
21:37:02:564
State Machine(:app.ui/RootRouter) :waiting! 
{}
21:37:02:562
(app.mutations/ensure-loaded!
 {:app
  {:com.fulcrologic.fulcro.application/id
 ... really long text ...
21:37:02:533
State Machine(:app.ui/RootRouter) :route! 
{:path-segment ["view" "ccb73fea-46a6-51ce-8e66-0fec960f45ef"],
 :router
 [:com.fulcrologic.fulcro.routing.dynamic-routing/id
  :app.ui/RootRouter],
 :target [:view/id #uuid "ccb73fea-46a6-51ce-8e66-0fec960f45ef"]}

nivekuil06:08:58

probably related: this also seems to break the "UI is a pure function of state" guarantee: it seems some duplicate items stay around, while only one of them is represented in the db. am I supposed to explicitly evict the previous route somehow?

tony.kay14:08:14

React keys?

nivekuil20:08:09

The items in the list have unique react keys, yeah -- no warnings in console. The duplicated items I see have the same react key (they are the same item after all, in different lists).

nivekuil20:08:33

maybe this is a misuse of react on my part? I think all I'm asking fulcro to do is flip between two ["view" view-id]s

tony.kay21:08:50

So, Fulcro schedules a render at transaction boundaries and remote results. Those run on RAF boundaries (16ms). The view is a f(state) is sometimes a disadvantage in that you sometimes end up being able to visually see “transient” steps that you would rather not. Dynamic routing does several steps through transactions, and it may render a child before the props are as you would want them. There is an :after-render option for transact that gives you some control on this; otherwise you have to use things like “ready” flags in state that you use to short-circuit rendering. Other glitches (duplication) are typically react-isms. Fulcro sends the view you construct…but if you misuse React you can get weirdness.

Adam Helins14:08:35

Hi! I have been learning about fulcro in order to replace re-frame. So far, I am rather impressed! Still learning though. I was wondering how good (or bad) of an idea it is to use the DB to store things that are not strictly relevant to the view.

tony.kay14:08:23

totally fine, generally recommend you only do that with serializable (transit) things. See also shared and it is also legal to put nsed keys into the app itself under the runtime-atom, which is not queryable for UI, but is useful for app extensions and things.

Michael W14:08:31

I cloned this repo: https://github.com/fulcrologic/fulcro-rad-tutorial I tried changing the datomic driver from :mem to :free, and supplied the host and port setting. I setup datomic free without an admin or storage password, and can access it from my own code with no issues. When I try to start up the tutorial though I get this error:

#error {                                                                                                                
 :cause "AMQ119007: Cannot connect to server(s). Tried with all available servers."                                     
 :via                                                                                                                   
 [{:type java.lang.RuntimeException                                                                                     
   :message "could not start [#'com.example.components.datomic/datomic-connections] due to"
   :at [mount.core$up$fn__42338 invoke "core.cljc" 92]}
  {:type clojure.lang.ExceptionInfo
   :message "Error communicating with HOST localhost on PORT 4334"
   :data {:alt-host nil, :peer-version 2, :password "itdura/K5qNL8B9+7Q25JylAzgzmISy+dlfmbm8btf8=", :username "MaUBCY72q
XAbqyJEAiLulEnoIUtCdnBw8IUQK+OxQUY=", :port 4334, :host "localhost", :version "0.9.5703.21", :timestamp 1597328653070, :
encrypt-channel true}
   :at [datomic.connector$endpoint_error invokeStatic "connector.clj" 53]}
  {:type org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException
   :message "AMQ119007: Cannot connect to server(s). Tried with all available servers."
   :at [org.apache.activemq.artemis.core.client.impl.ServerLocatorImpl createSessionFactory "ServerLocatorImpl.java" 799
]}]
 :trace

3
Michael W14:08:31

This ended up being a mismatch of versions for datomic-free. deps.edn had 0.9.5697 but I needed 0.9.5703.21 (installed locally).

🎉 3
Adam Helins14:08:08

Just so I understand, things that are never part of any query don't need to be serializable I guess. Sometimes it is useful to mix JS objects in the model, although it looks a bit dirty. For instance, a music player which does its own thing (playing music) but has some elements of interests to the view (eg. current position, current length). Plus, I would like to store such objects in a normalized way. At first, it looks easier to put them in the fulcro DB rather than maintaining a separate atom, doing normalization myself and periodically updating pure data (eg. current position) in the fulcro DB.

tony.kay14:08:54

This has little to do with the query, though it is true that a query cannot walk js objects, the reasoning is more as follows: • Fulcro inspect has to transmit the values…tools are not in the same VM. You put things in the db that are not ok in that, and the tools can break in strange ways. • Communication with the server often includes (even accidentally, where for example you accidentally pass a map that has “too much”) things from the db. If you get things in there that are not serializable, comms will break. • Support: You could technically serialized the db (or a series of them) for use in debugging issues…i.e. as part of an issue report system. Devs could then spin up that real app state history in a dev env from this issue report using the version of sw that the user is running. Putting things in db that are not serializable breaks this. That said, so does component-local state, since it would be missing as well. • Less tangible reasons: “just data” in the database makes things easy to read/look at/manipulate. You can easily convert clj to js data in render if necessary, and it is “fast enough”. Mixing stuff in db that is not clj data leads to convoluted code.

👍 3
tony.kay15:08:47

So, as a best practice I put serializable data in the db. If I really really need speed, I consider component-local state…but only really as an optimization when it is proven to be too slow otherwise.

JAtkins16:08:48

Is there any way to generate a new component with some id? I'm trying to get a general method to go from

full component props (incl. id) + class -> new mergeable props w/ new id 

Jakub Holý (HolyJak)17:08:03

I don't understand. You want to take the current props and just replace the ID prop with a new value? And you don't know how to find out what is the ID property? I think :ident whatever will be replaced with a fn of props -> ID value so you can't get the prop key from it.

JAtkins17:08:20

I'm basically trying to clone a component given props in the db and the component that renders it

JAtkins17:08:35

To make a copy I need to replace the id

Jakub Holý (HolyJak)17:08:44

And the problem is...?

JAtkins18:08:35

I don't know the id keyword beforehand

bbss18:08:40

(comp/get-ident Class props)

tony.kay18:08:22

What @U09MR0T5Y said. Even if you send empty props you’ll get back an ident whose first element is the keyword you need.

tony.kay19:08:13

The magic of the macro turns :ident into a lambda if it isn’t.

JAtkins19:08:26

Oh, duh...... Should have thought of that

JAtkins16:08:55

I think the issue may be that the template mode of defsc :ident (`:ident :keyword`) actually doesn't retain the location information, but could be wrong