This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2019-12-11
Channels
- # adventofcode (31)
- # announcements (6)
- # babashka (5)
- # beginners (93)
- # calva (15)
- # cider (20)
- # cljs-dev (16)
- # clojure (159)
- # clojure-berlin (1)
- # clojure-dev (5)
- # clojure-europe (9)
- # clojure-italy (9)
- # clojure-losangeles (2)
- # clojure-nl (26)
- # clojure-spec (7)
- # clojure-uk (33)
- # clojurescript (54)
- # clojutre (5)
- # cursive (20)
- # datomic (23)
- # emacs (19)
- # events (1)
- # expound (1)
- # figwheel-main (1)
- # fulcro (104)
- # hyperfiddle (1)
- # jobs (1)
- # luminus (10)
- # malli (59)
- # nrepl (1)
- # off-topic (11)
- # pathom (5)
- # planck (15)
- # reagent (13)
- # reitit (8)
- # rewrite-clj (10)
- # ring-swagger (3)
- # shadow-cljs (129)
- # tools-deps (46)
- # xtdb (14)
- # yada (1)
Hi there! I use the version 3.0.12. There is a strange something about the state machine's handler, it seems that uism/assoc-aliased won't work before the dr/change-route. the codes like this:
:event/goto-club-page {::uism/handler
(fn [{::uism/keys [event-data] :as env}]
(uism/assoc-aliased env
:selected-club
(:club/id event-data)) ;; it doesn't work.
(dr/change-route SPA
["main" "currentClubPage"])
)}
:event/goto-club-page {::uism/handler
(fn [{::uism/keys [event-data] :as env}]
(dr/change-route SPA
["main" "currentClubPage"])
(uism/assoc-aliased env
:selected-club
(:club/id event-data)) ;; it works.
)}
Have anyone know why is it?
Just a slack tip: You can enclose multi-line code snippets in triple backticks.
@tony.kay thanks a lot, it’s working as expected now, pre-merge made the app’s logic much cleaner
coming from React/ES6-land: is there any equivalent of PropTypes declarations in Fulcro (for requiring certain props)?
@jakobssonrobin Clojure spec is your friend. As you learn more about Clojure and the general approach to name-spaced keywords (which stems from RDF) you’ll find that hard-core schema hurts you more than it helps. You’re also in a language where new “syntax” is just a macro away…so, the short answer is “not exactly”, and the longer answer is “sort-of, but a bit different”, and the more involved answer is “you can easily get something better” (for example the options map on components is an open map, which means you can put your own nsed declarations (e.g. about prop specs) and then a simple wrapper macro could add in devel-time checking.
Thanks, I will have a look at spec 🙂 . And I’ll give nsed declarations a thought.
Fulcro 3.0.13 and Fulcro Websockets 3.0.5 released. These fix some internal issues that were affecting correct operation of :pre-merge
and load normalization.
Is there a concise way to mark form fields as complete and then immediately check validity? Validation check requires the denormalized entity tree, and props aren’t updating that fast if I’m transacting fs/mark-complete! and then pass props to the validator. I’ve got 2 solutions: to manually denormalize required component from normalized state in mark-complete transaction and to use js/setTimeout. Both seem quirky...
You mean within a mutation, right?
I should really add a convenience version of the function for that. You can denormalize things with com.fulcrologic.fulcro.algorithms.denormalize/db->tree
and use the existing function.
or you could look at the implementation and write the helper that works on normalized state.
Basically you define what it means for a field to be complete, and the ::fs/complete?
entry in the form config tracks completeness…so you can always figure it out in a mutation from base data that you have easy access to. But if you’re trying to use a generated validator from make-validator
, then using db->tree
is probably easiest.
There is a form-state helper function called update-forms
that walks over your (potentially) nested for via normalized state. You could combine that with an atom in a helper function to build something that could directly walk normalized state and use your same predicate you gave to make-validator
. I supplied update-forms
as a helper for building whatever you might need that works across form sets.
Thank you for the ideas, update-forms
sounds promising. But I’m using spec validation, and there’s another caveat: I must check validity before calling dirty-fields
, because it is instrumented (?) and fails on my specs. So I can’t just pass the dirty diff to the mutation which would mark the form complete, check for validity and submit — spec fails before that. So my implementation with db->tree
within a mutation failed and I went with js/setTimeout
.
To the first question, I meant within a mutation + in the same transaction (in case we’re using fs/mark-complete!
, so that mutations would execute in the right order, but now I come to think about and it seems to not really matter, as optimistic transactions’ action
s are executed asap, right?)
That’s kinda funny how I ended up implementing a similar transactional mechanics in re-frame
for past projects, before learning about Fulcro. Thought I was doing something wrong cuz I haven’t found anything like that for re-frame
back then. Good to know it wasn’t such a misconception on my side. Better though to finally switch to a battle-tested implementation;)
So, you are using instrumented specs to throw on validation? That seems very off to me. There’s nothing wrong with using specs, and there are API calls to evaluate those when/where you need to. Am I missing something?
No, I’m using specs only for fs/get-spec-validity
, I’m not instrumenting anything. But Fulcro somehow throws on calls to fs/dirty-fields
, when my fields aren’t valid.
I’m getting an error when I try to load an ident-backed router-target detail comp using a deferred load triggered from the :will-enter
prop on the detail comp:
Storing :target -> [:sitevisit/id "17592186213031"] on :riverdb.ui.sitevisits-page/SVRouter
Doing merge and targeting steps: {[:sitevisit/id "17592186213031"] {:sitevisit/id "17592186213031", :db/id "17592186213031"}}
Doing post mutation com.fulcrologic.fulcro.routing.dynamic-routing/target-ready
ERROR - dr/target-ready! was called but there was no router waiting for the target listed: [:sitevisit/id "17592186213031"] This could mean you sent one ident, and indicated ready on another.
any particular reason to use strings for IDs? Seems strange (but harmless as long as they match). Returning route-deferred
on will-enter
tells the router state machine to expect a future call to target-ready
. The router then stores that, and when the call comes, it looks it up. If they don’t match it issues this error.
If you used route-immediate
or didn’t match the idents, then you’ll see the error…if you did the former it will appear to still work, since you already approved the route.
@U07KXN95L I’ve got the same issue, turned out you have to pass initialized props to the router (e.g. initialized via component’s :initial-state
), otherwise the router won’t have an id.
@fjolne.yngling ah yes I wondered about that but I didn’t figure out how to do it. I’ll look into it. @tony.kay yes I wound up switching to a route-immediate instead. I use strings for keys because they’re Datomic IDs which are longs and which javascript can’t handle in the higher value range. This was my original will-enter and as you can see I’m literally using the same ident for the route call and the target-ready call (straight out of the example in the guide)
:will-enter (fn [app {:keys [sv-id] :as params}]
(log/debug "WILL ENTER" sv-id params)
(dr/route-deferred [:sitevisit/id sv-id]
#(f/load app [:sitevisit/id sv-id] SiteVisitEdit
{:post-mutation `dr/target-ready
:post-mutation-params {:target [:sitevisit/id sv-id]}})))
@fjolne.yngling what did you do to work around?
specifically…you passed your own :initial-state
to defrouter
? Or used initial state in the children (which is something you should do)
If we can confirm a bug, I’ll look into it. I personally don’t use the deferred support much. Turned out to be less useful than I thought, since I usually would rather give the user the feedback of routing first, and then show a loader.
(defrouter YourComponentRouter [this props]
{:router-targets [YourTarget1 YourTarget2]})
(def ui-your-component-router (comp/factory YourComponentRouter))
(defsc YourComponent [this {:your/keys [router]}]
{:query [{:your/router (comp/get-query YourComponentRouter)}]
:ident (fn [] [:component/id :your-component])
:route-segment ["your" "segment"]
:initial-state {:your/router {}}}
(ui-your-component-router router))
Right. Exactly. But I guess there could be a bug for deferred routing to something you are loading (which won’t be in state yet).
so this is bringing up something which is that I guess I’ve been assuming that :initial-state is optional, but it seems necessary in several cases
So, initial state is required when you want that part of the graph to pre-exist in the db at app start…simple as that
so the router will take care of initializing state even for all of its route targets?
but I might have some kind of initialization logic error that won’t detect the route target if it has no initial state
I think I had initial-state in the route target component, but i didn’t initialize the router itself
ok I just switched back to route-deferred and got the error again. I do have :initial-state on the route target, and this is even routing to something that already has data in the DB under its ident
The logic for deferred routing is pretty trivial: https://github.com/fulcrologic/fulcro/blob/develop/src/main/com/fulcrologic/fulcro/routing/dynamic_routing.cljc#L262
If you look in fulcro inspect db, and slide back in time while watching the state of the ::dr/asm-id of your router, perhaps that’ll give you a clue
and I am giving the router initial-state as @fjolne.yngling suggested
The state machine is small and declared : https://github.com/fulcrologic/fulcro/blob/develop/src/main/com/fulcrologic/fulcro/routing/dynamic_routing.cljc#L270
no it wasn’t given in the defrouter call, that’s just: `
(dr/defrouter SVRouter [this props]
{:router-targets [SiteVisitList SiteVisitEdit]})
So, target ready error comes from this check: https://github.com/fulcrologic/fulcro/blob/develop/src/main/com/fulcrologic/fulcro/routing/dynamic_routing.cljc#L91
which looks through the routers in state trying to find one that is waiting on the ident you specified
the ident is either there or it isn’t (and it gets put there when you do deferred return value)…and it either matches on =
or it doesn’t…which is also up to the values you use
the ident matches because the route-immediate works using the same ident, and in the route-deferred I’m literally using exactly the same ident for the route and for the target-ready
FWIW here’s the console log. There is a render of the page that hosts the router. would that make a difference?
DEBUG [com.fulcrologic.fulcro.ui-state-machines:123] - Triggering :route! on :riverdb.ui.sitevisits-page/SVRouter with {:error-timeout 5000, :deferred-timeout 100, :path-segment ["edit" "17592186213031"], :router [:com.fulcrologic.fulcro.routing.dynamic-routing/id :riverdb.ui.sitevisits-page/SVRouter]meta, :target [:sitevisit/id "17592186213031"]meta}
DEBUG [com.fulcrologic.fulcro.ui-state-machines:?] - Storing :pending-path-segment -> ["edit" "17592186213031"] on :riverdb.ui.sitevisits-page/SVRouter
DEBUG [com.fulcrologic.fulcro.ui-state-machines:?] - Applying mutation helper to state of :riverdb.ui.sitevisits-page/SVRouter
DEBUG [com.fulcrologic.fulcro.ui-state-machines:?] - Activating state :deferred on :riverdb.ui.sitevisits-page/SVRouter
DEBUG [com.fulcrologic.fulcro.ui-state-machines:?] - Storing :target -> [:sitevisit/id "17592186213031"]meta on :riverdb.ui.sitevisits-page/SVRouter
DEBUG [com.fulcrologic.fulcro.ui-state-machines:?] - Setting timeout :error-timer on :riverdb.ui.sitevisits-page/SVRouter to send :timeout! in 5000 ms
DEBUG [com.fulcrologic.fulcro.ui-state-machines:?] - Setting timeout :delay-timer on :riverdb.ui.sitevisits-page/SVRouter to send :waiting! in 100 ms
DEBUG [com.fulcrologic.fulcro.algorithms.indexing:58] - (Re)indexing application query for prop->classes
INFO [com.fulcrologic.fulcro.networking.http-remote:321] - merge-edn => [{[…] […]meta}]
DEBUG [com.fulcrologic.fulcro.ui-state-machines:?] - TIMEOUT on :riverdb.ui.sitevisits-page/SVRouter due to timer :delay-timer after 100 ms
DEBUG [com.fulcrologic.fulcro.ui-state-machines:123] - Triggering :waiting! on :riverdb.ui.sitevisits-page/SVRouter with {}
DEBUG [com.fulcrologic.fulcro.ui-state-machines:?] - Activating state :pending on :riverdb.ui.sitevisits-page/SVRouter
RENDER SiteVisitsPage [list]
XHR finished loading: POST "".
DEBUG [com.fulcrologic.fulcro.data-fetch:154] - Doing merge and targeting steps: {[:sitevisit/id "17592186213031"] {}} [{[…] […]meta}]
DEBUG [com.fulcrologic.fulcro.data-fetch:159] - Doing post mutation com.fulcrologic.fulcro.routing.dynamic-routing/target-ready
ERROR [com.fulcrologic.fulcro.routing.dynamic-routing:110] - dr/target-ready! was called but there was no router waiting for the target listed: [:sitevisit/id "17592186213031"] This could mean you sent one ident, and indicated ready on another.
again, use Inspect to examine the router’s state history…that pending stuff is saved on the router itself
OK, I’ve tried all of the different variations (and fixed the bugs I introduced since my original message that interfered with the investigation) and I can now definitively agree with @fjolne.yngling that getting the router’s initial state into the comp that hosts it is essential like so:
(defsc SiteVisitsPage [this {:keys [svrouter] :as props}]
{:initial-state (fn [params]
{:svrouter (om/initial-state SVRouter {})
I had it partially working by only doing `
:svrouter {}
But by partially working I mean that I loaded my list page via a load! in :componentDidMount
(whereas :will-enter
did not work when I just tried it, even with a route-immediate) and therefore when I DID try to use :will-enter
on the detail page, deferred routing failed.
and if you didn’t use the fn version of initial state, you could have used an empty map
:initial-state {:sv-router {}}
=== (fn [p] {:svrouter (comp/get-initial-state SVRouter {})})
OK, so this is emphasizing to me the importance of always doing initial-state which I’d thought was optional
The things you use in the logic MUST be in state. :initial-state
is just one way of getting them there (at app startup)
does the state from a secondary route get initialized at app start? If not, does it then get the class’s :initial-state immediately before loading it?
You are responsible for populating state in all circumstances. The dynamic router will attempt to statically resolve all initial state inclusions for it’s immediate children (by composing them in), but you must compose the parts you control…if you don’t then you must add them to state during runtime