This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2020-08-13
Channels
- # babashka (10)
- # beginners (27)
- # calva (91)
- # cestmeetup (3)
- # cider (27)
- # clj-kondo (12)
- # cljs-dev (4)
- # clojars (13)
- # clojure (35)
- # clojure-europe (30)
- # clojure-france (3)
- # clojure-houston (1)
- # clojure-nl (11)
- # clojure-norway (29)
- # clojure-spec (23)
- # clojure-sweden (5)
- # clojure-uk (128)
- # clojurescript (69)
- # conjure (44)
- # core-async (27)
- # cursive (13)
- # emacs (9)
- # events (3)
- # fulcro (52)
- # graphql (4)
- # jobs (2)
- # jobs-discuss (46)
- # kaocha (4)
- # luminus (12)
- # nrepl (10)
- # off-topic (29)
- # re-frame (17)
- # reitit (20)
- # remote-jobs (4)
- # rewrite-clj (1)
- # ring (4)
- # rum (13)
- # shadow-cljs (40)
- # sql (1)
- # xtdb (1)
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.
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
??
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.
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
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.
::report/source-attribute
could then be ignored. However you would have to put something in there because it is a required attribute.
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.
On 1.0.0-RC2-SNAPSHOT @U0D5RN0S1
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...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.
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.
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.
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
same with pagination: it’s a very fast constant-time subvec
of the final display rows…no need to do filter/sort
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.
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.
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
.
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?
I'm not sure what I can do differently with a custom UISM that avoids this problem the dynamic routers have
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"]}
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?
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).
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
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.
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.
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.
@tony.kay Great, thanks!
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
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).
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.
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.
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.
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
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.
I'm basically trying to clone a component given props in the db and the component that renders it
And the problem is...?
What @U09MR0T5Y said. Even if you send empty props you’ll get back an ident whose first element is the keyword you need.