This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-04-08
Channels
- # announcements (8)
- # aws (2)
- # babashka (11)
- # babashka-sci-dev (39)
- # beginners (62)
- # calva (5)
- # cider (1)
- # clj-kondo (50)
- # cljdoc (2)
- # cljs-dev (6)
- # clojure (52)
- # clojure-austin (22)
- # clojure-czech (13)
- # clojure-europe (88)
- # clojure-nl (1)
- # clojure-norway (5)
- # clojure-uk (6)
- # clojuredesign-podcast (13)
- # clojurescript (45)
- # community-development (3)
- # core-typed (31)
- # cursive (12)
- # datahike (2)
- # datalevin (7)
- # datomic (5)
- # events (1)
- # exercism (11)
- # fulcro (27)
- # gratitude (1)
- # holy-lambda (3)
- # hoplon (2)
- # introduce-yourself (2)
- # jobs (1)
- # lambdaisland (3)
- # lsp (110)
- # malli (2)
- # meander (4)
- # music (2)
- # off-topic (50)
- # overtone (1)
- # pathom (13)
- # polylith (26)
- # re-frame (4)
- # releases (2)
- # rewrite-clj (3)
- # ring (12)
- # shadow-cljs (20)
- # specter (4)
- # tools-deps (8)
- # xtdb (40)
I'm trying to customize fulcro-inspect
I started the shadow watcher and loaded the build by selecting the shells/chrome
folder just like the readme says.
However, the hot-reload doesn't seem to work correctly. The shadow watchers work and build everything just fine, but the Inspect UI doesn't refresh and also doesn't load the changes when I hit F5. So, I assume I'm doing something wrong?
Okay, it reloads the changes reliably when I close and re-open the DevTools, that's something.
Is there anything special to account for when using hooks with fulcro re timing? I'm trying to use the Sign In With Google library, which expects that something renders a dom element with magic id, and when its initialize function is called, it will look for it and change it into Google's login button. I'm calling the initialize in the button component's useEffect, but I keep getting errors quite often that the initialize was called before the button was rendered.
Hmh, running js/Document querySelector in the effect function does always return the element
If I remove the magic id, the error disappears. So "Failed to render button before calling initialize()" must actually mean something else than what I though, and this is not a fulcro thing at all
> Failed to render button before calling initialize() Yeah, I've got the fulcro sources here and such an error message doesn't exist in there. The messages origin in the console tab should be pointing to somewhere in that google lib then(?). Maybe you can step into that code and look up what's going on
Oh, it's definitely from the google lib, but I assumed it meant that in the react sense it cannot find the button in the dom. But it is there, and the error is something else. Unfortunately the google lib is minified and I don't know if there's any place where it would be in original form. The error message itself doesn't get any good search results
I started looking at the google lib with a debugger, and it is actually running react too
@tony.kay Is there anything wrong with initializing my routers like this when the application is starting?
(let [routers (dr/all-reachable-routers (app/current-state app) (app/root-class app))]
(doseq [router routers]
(mrg/merge-component! app router (rc/get-initial-state router {})))
I have a dynamic router in a component that is dynamically loaded. However, the change-route!
call that causes this component to be loaded (and therefore the initialization of the router via pre-merge
) also concerns the router in it.I’m not sure I follow completely. Routers pre-compose themselves into app state as long as you did your job right and composed them up to root. If you have dynamically-loaded code, then yes, you need to merge them into state AND set the query on the parent to point at the router.
@tony.kay With a component like this:
(defmutation init-child [{name :name}]
(action [{:keys [app]}]
(mrg/merge-component! app Child
{:name name
:sub-router (comp/get-initial-state SubRouter {})})))
(defsc Child [_ _]
{:ident :name
:query [:name {:sub-router (comp/get-query SubRouter)}]
:segment ["child" :name]
:willEnter
(fn [app {:keys [name]}]
(let [ident (comp/get-ident Child {:name name})]
(dr/route-deferred ident
#(comp/transact! app
[(init-child {:name name})
(dr/target-ready {:target ident})]))))})
That component doesn't have an initial-state, because it needs the :name
for that.
Now, I can't change to a route like that: ["child" "childs-name" "route-target-of-sub-router"]
Because the SubRouter
wasn't part of the Root
's initial-state, because Child
can't have initial-state.I can't use pre-merge
because I route with the SubRouter before I merge any data for Child
.
I could write: :initial-state {:sub-router {}}
But then I have a nil
key in my database, because that initial-state is missing the name that I don't know yet.
The only option would be that I route to ["child" "childs-name"]
first, let it initialize, and then change-route
after that.
I really don’t follow…initial state is about just that: initial state. The router, is, in fact, initial state, so it should be included. You’re saying you don’t know :name
yet? Who cares? Don’t render if it isn’t set yet (e.g. encode render body with when-not name
you can’t possibly have a will-enter that will work right if the router isn’t in state yet
I know that. That's why I add the initial state of every router on client startup with:
(defn initialize-routers! [app]
(let [routers (dr/all-reachable-routers (app/current-state app) (app/root-class app))
merge-initial-state (fn [state component] (mrg/merge-component state component (rc/get-initial-state component {})))]
(swap! (::app/state-atom app)
(fn [state] (reduce merge-initial-state state routers)))
(dr/initialize! SPA)))
And I don't rely on :initial-state
at all.
The only way the docs tell how to add the routers initial-state to the db, is through :initial-state
and :pre-merge
From https://book.fulcrologic.com/#_composing_the_routers_state_into_the_parent :
> 1. Include the router’s initial state in the parent’s initial state
> 2. Include the router’s initial state in the dynamically-loaded state to ensure an edge between the two
> 3. Make sure to preserve any state the router might already have
I basically replace step 1 and initialize the router on client startup.
(of course, I add the edge to the router's parent state later)
But since dynamic routers work with (scary) dynamic queries, I wanted to make sure, that I am not missing something important.
I think maybe that piece of code could go into dr/initialize!
so there would never be not initialized routers again…So the primitive bits are there. Dynamic queries are just part of app state, and the initial static query of every router includes all targets (and all initial state). So, manually merging the initial state of routers is fine. NOTE: You can (and probably should) use the component registry to find the routers instead of all-reachable-routers
, because the latter uses the query of the root component, and if you didn’t compose some routers in, then you’re not doing what you wanted anyway.
I don’t see the resistance to :initial-state. It’s an elegant solution that has worked very well for me for the entire life of the project,. perhaps I’m missing something in your use case. Seems like you’re making trouble for yourself where there isn’t any necessary.
If you’re doing code splitting and need to load something that has a dynamic route in it as the ultimate target, then you’re going to have to do some gymnastics, for sure. I think what I’d do is make a place in state where my intended final route could be stored, and a wrapper function that knows where the dynamically loaded boundaries are…so, when you call that wrapper (probably a UISM or something simiular) with a route that needs loading, it would: 1. Store the full target path in a temporary location 2. Trigger the code load 3. The loaded module would have a post-step that merges initial state of the loaded components 4. The loaded module sends a signal to the UISM saying it was loaded 5. The UISM would then trigger the final route
i.e. treat dr
as a more primitive layer, and stack your module-aware routing on top of it.
Let me clarify:
1. I am not doing any dynamic code loading.
2. I can't use :initial-state
, because I don't know a value for the ident at that point. (`Child` is a router target of another router)
3. I can't use :pre-merge
to initialize the state of the router, because the change-route!
triggering it, is also triggering the route change for the sub router (which hasn't state in the db at this point of time)
But since routers are singletons in the application, there is no need to rely on a (like in this case) broken initial-state tree?
I think I am in a similar situation when you hard-reload a particular page in my app here https://github.com/holyjak/fulcro-billing-app/blob/main/src/shared/billing_app/client.cljs#L161 - in particular the https://github.com/holyjak/fulcro-billing-app/blob/main/src/shared/billing_app/ui/billing/ui.cljc#L428 is dynamically loaded and contains a child router (`br/details-router`) so if you navigate e.g. to /org/<some-org-nr>/employees
I want to configure this child router (it has also a parent router but that does not matter here).
I use initial-state (so I will get the "dummy" entry {:organization/organization-number {nil {:br/details-router [..], ..}}
in the db) and pre-merge together to ensure that everything is linked correctly and that Fulcro can find all the routers in the state.
My struggle to get this working lead to https://book.fulcrologic.com/#_composing_the_routers_state_into_the_parent Any improvement suggestions to the section are most welcomed :)
Thanks for the clarification. I see your problem now. If you wanted to make a global initialization that seems fine, and you can use the global registry to find all of the routers. Again, I would not use all-reachable-routers, because the router in question might not be reachable. But then it is just a simple loop of merge-component
using the initial state of the router’s themselves.
use component-options
to see the options being passed in the component initialization maps. The ones with router-targets
are routers.
When does a router enter the global registry? If there is no instance of Child
at the app start and it has no :initial-state
, will its child SubRouter
still be in the global registry?