Fork me on GitHub

Do you ever use hash as a React key where there is no more sensible choice? e.g. (prim/factory MyComponent {:keyfn hash})


With dr/route-deferred can I issue a call to prim/transact! and do (dr/target-ready* target) in the mutation?


@hmaurer don’t do that. key should be stable. See React docs. You only need a key if that component will appear in an array with siblings of the same type, in which case you can use map-indexed or ids


Hmm I am aware of the react doc on the topic, but a hash of the data seems more stable to me than an index :thinking_face:


the data changes in ways that don’t “move” the component


(ui-x {:x 1}) -> (ui-x {:x 2}) should NOT unmount and remount the component…it should update the props


setting the key to a hash of that data WILL force it to unmount and remount, and a hard DOM flush…very very bad for performance


so, the only thing worse you could do is set it to rand-int


Ah, I see what you mean. Fair point; thanks for the explanation 🙂


sure…you only need key if the component instances will appear in an array/sequence…they can be hard-coded siblings. The key is for places where React detects that the list of things can change over time


that’s why adjacent divs don’t need them


but anything output from map does


@tony.kay oh yes that I know. I was only considering using hash as a key for sequences, instead of an index


@pvillegas12 That is the intended usage, yes, but I think I see a bug in the source…that will not move you to the new correct state in that router’s state machine 😕


Doing a (uism/trigger! reconciler router-id :ready!) in the mutation seems to transition the state machine correctly, but I’m not sure if doing

(let [router-id (dr/router-for-pending-target @state target)]
      (uism/trigger! reconciler router-id :ready!)
      (swap! state
        (fn [s]
          (-> s
            (target-ready* target)
            (assoc-in ...)))))))
is the way to go


no, not very clean


you shouldn’t need to know anything about that


@tony.kay I’m also hitting another roadblock with dynamic routing. When I change route, target-ready* will side-effect only when it can find the router-id in the normalized database. However, when I trigger the route change, the state will not have the :fulcro.incubator.dynamic-routing/pending-route part yet and it will not transition. Clicking on the link again works as the pending-route part is now in the db.


@pvillegas12 if you use a deferred route you should never immediately mark the target ready. But, this is a similar issue as above and in the issue: The state machines for all routers should start on app start.


This seems to work

:will-enter      (fn [reconciler {:keys [model-id]}]
                      (let [target [:model/by-id model-id]]
                        (prim/transact! reconciler
                          `[(mut/add-model ~{:target         (conj target :model/form)
                                             :form/component Model})])
                        (dr/route-immediate target)))
Not sure if it respects the no side-effecting on the :will-enter hook


I see that I put on the doc string that it should be side-effect free, but I don’t remember why. Oh, because will-enter will be called from within a running mutation. So, technically, you can do this if you use ptransact! (which defers its transaction). Calling transact inside of transact usually “does what you want”, unless the operation in question was called from within a swap! on the state atom, in which case weirdness will ensue.


Thanks for sticking with the new router…I think it is a big improvement, and it’ll be nice to get these kinks worked out of it.


Can I call a transaction with the immediate call?


@pvillegas12 See I mean for you to use deferred in that case, but it seems like there is a bug that keeps that from working correctly. The workaround is to use ptransact!. transact! “may” work, but it ends up running the new transaction “nested”, which isn’t technically safe.


The work-around is using ptransact! within within the will-enter hook and calling (dr/route-immediate target) right? My use case in particular is building a model when a route is entered. I can resolve the route immediately, but I want to perform a mutation corresponding to building the model.


technically I think route-deferred is what we want, with a target-ready* inside the mutation, but that helper isn’t right


Correct, route-deferred should be able to receive a mutation calling target-ready*, I agree.


I am looking at the form support. I am trying to make a simple client-side form, and I don’t really get what I should put in my :query and :ident. Filling the form is client-side only, and I guess I will have to make a mutation on the submit event.

(defsc SubscribeForm [this {:keys [channel] :as props}]
  {:query [:ui.sub-form/id      ;; not sure what that will be, but what would I put as ident otherwise?
   :ident [:ui.sub-form/id] ;; what will fill the ID there?
   :initial-state {:channel "#demo"}
   :form-fields #{:channel}}
    (dom/h3 "PubSub Form")
      (dom/input {:type "text"
                           :value channel   ;; is `nil` here - why since I have an initial-state?
                           :required nil})
      (dom/input {:type "submit"
                  :value "submit"}))))


I use [:model/by-id :db/id] as ident in those form components


if it is a single-use thing that has no ID, just make up an ident like :ident (fn [] [:FORM/by-id ::subscribe]), nothing needed in query for the ID since you have none. But you must include the form config in your query


What is the difference between fn idents and vectors idents?


vector-based uses first ele as literal, second to pull from props

💡 5

fn form lets you use props from arg list to do it however you want, including just sending a constant ident


or calculating it somehow


arg list of defsc


@tony.kay for some reason, on the component

(defsc-route-target CompanyDetails
  [this {:keys [] :as props}]
  {:ident           [:company-details/by-id :company-id]
   :query           [:db/id :company-id]
   :route-segment   (fn [] ["empresa" :company-id])
   :route-cancelled (fn [_])
   :will-enter      (fn [_ {:keys [company-id]}]
                      (println "CompanyDetails:" company-id)
                      (dr/route-immediate [:company-details/by-id company-id]))
   :will-leave      (fn [_] (js/console.log "Leaving CompanyDetails") true)}
  (dom/div {:onClick #(my-mutation this)})
I’m calling (prim/get-ident comp) on a mutation used by this component and getting nil for company-id even if I am getting hte correct id in the will-enter hook.


Also getting the warning


component's ident ([:company-details/by-id nil]) has a `nil` second element. This warning can be safely ignored if that is intended.
when I trigger the route change


what is comp?


a class or instance?


in this case the instance of CompanyDetails


calling (my-mutations this) and the implementation calls (prim/get-ident this)


sure? Not the class, but a true react mounted instance?


the only reason you get that is if that component has empty props, basically


mmm, as it is a defsc-route-target, where should I get the props from?


which is happening because your route is going there before your mutation puts the data in place


which is why you need deferred in the first place 🙂 So, our workaround won’t work…you’ll need to use deferred with ptransact!


Actually, I made it so that I would build the model after clicking on a button


the props aren’t in state on that render, which is why you’re getting the warning


I’m confused now, currently doing

:will-enter      (fn [_ {:keys [company-id]}]
                      (println "CompanyDetails:" company-id)
                      (dr/route-immediate [:company-details/by-id company-id]))
after going to that route, I would expect the fulcro db to show [:company-details/by-id { company-id: []}].


Or are you saying I should make a mutation which creates that ident manually? Something like (assoc-in state-map [:company-details/by-id company-id] { ... })?


Basically, I have a list of companies in the database. Clicking one (which is already loaded in the normalized db) routes to a company details page. As I already have it in memory, I’m not doing any loads or deferred, just immeadiately routing there.

Joe Lane22:02:03

Alright friends, I’m back again. So I’m trying to use the aws js cognito sdk. Last time I showed up I got the impression that a remote was the place to call that auth code. Turns out that remotes are only for network operations now, meaning that the “remotes as an abstraction” blog post must be no longer true. Where is the proper place in fulcro to put the client side auth code to interact with cognito?


@pvillegas12 ah, well, then there should be props


so, I’m confused as to how you’re seeing that warning


but that’s what it means


Going back to the basics, I was pointing at the incorrect ident in the fulcro database, that’s why it could not find the id correctly. I was doing [:company-details/by-id :company-id] instead of [:company/by-id :db/id].


@joe.lane nothing is out of date about that blog post, other than there might be newer ways of building a remote


don’t remember when I wrote it


Yeah, there are better ways of making a remote now


client supports middleware

Joe Lane22:02:10

So, this remote basically wont be hitting my backend server

Joe Lane22:02:26

instead it will be routed to cognito (third party)

Joe Lane22:02:34

Is that still a concern of middleware?

Joe Lane22:02:42

Ok thanks, I’ll take a look again.


do you need to “redirect” the page?

Joe Lane22:02:51

No, its a SPA

Joe Lane22:02:03

Well, once authed, I redirect


so you’re rendering the login form

Joe Lane22:02:39

Yes, I’m doing a passwordless login via sms. There are 3 different login component phases


so it is typicaly oauth kind of thing: You go to remote server to log in, and it redirects to your app


so at that point you have what? A cookie, a JWT token that came in params, what?

Joe Lane22:02:34

at that point I just need to call the cognito sdk and it is handles that for me

Joe Lane22:02:47

it shoves the jwt token in localstorage

Joe Lane22:02:57

and I never look at it again

Joe Lane22:02:16

My code originally was tangled up in mutations which I figured wasn’t the right place since it isn’t a reproducible transaction

Joe Lane22:02:52

Effectively I’m just trying to call the cognito library using the right mechanism and code organization in fulcro. Next though I’ll be doing the same thing but with s3 file uploads using the same sdk, so I want to make sure I’ve got a good understanding of the problem. Clearly I’m missing something.


so, you have a js-land lib that does that for you?


I’d put that call in started-callback/client-did-mount


do you then need to pull data from there and put it in fulcro state?


I mean, this is a “startup concern” as far as I can tell

Joe Lane22:02:31

Yeah, I believe so

Joe Lane22:02:57

I have 3 fulcro dynamic routes that I use for signing up users.

Joe Lane22:02:57

I wouldn’t consider it a startup concern if the fulcro app is used in the sign up process.


If you need it in initial state, you could also then transact it into state during startup. If you need to “redirect” them if they haven’t yet signed in, you might even delay loading the app until you see they have good creds (in js land, with exported functions). I did an app with oauth, and the “landing page” wasn’t even the real app. It was a very light js page that checked for creds and did a browser redirect to the real app or login. This kept the first page load really fast


but the point is you’ll never get the “creds” without a real redirect to a remote page the fulfills the auth, right?


sry, I don’t have time to read that

Joe Lane22:02:43

Its not a page redirect


ok, so the js lib is capable of doing it all, I see

Joe Lane22:02:27

( I REALLY appreciate your time, wasn’t suggesting to read all those docs)

Joe Lane22:02:46

I already have, several times over, 3/10 wouldn’t recommend


so, it is fine to make normal js calls. So, this what, sends an SMS with a code, then you have to tell the lib the code


stuff like that?

Joe Lane22:02:46

yup, but organizationally, is that a remote?

Joe Lane22:02:07

I tried modeling a solution after that blog post on remotes as an abstraction


man that lib is a model of modern OOP, eh?

Joe Lane22:02:38

Did I mention it mutates Local storage on my behalf haha

Joe Lane22:02:02

Also, callbacks everywhere, there are like 9 different js lib versions of this cognito thing too.


yeah, so using remotes will get the async mess out of your face. Alternatively, you could use all mutations. It is legal to call ptransact! in mutations (or even callbacks within mutations)…it just puts transact! in a setTimeout

Joe Lane22:02:26

The big concern is, 1. should a self contained component of functionality like this be a remote? 2. When I do s3 file uploading, it wont be a multipart upload to my server, instead I’ll be using the js s3 sdk, does that happen in a remote as well?


then just swap it all into state and use proper follow-on reads to get UI refresh


So, a remote in Fulcro is very simple: It essentially is a send function that is called with a request (the tx, essentially) and some functions like ok, error, and optionally update. When you call ok, it is essentially just Fulcro merge…that is to say it uses the tx and body you send it to put data in app state.


so, any async API could be “wrapped” in a remote, and result in any kind of app state change.


does not have to have anything to do with networking, even.


e.g. you could say “merge [:x] Using body {:x 1}” and you’ll get :x 1 in the root of your app db


if you use a true component query, then it’ll do normalization and everything


update accepts the same thing, but can be called more than once, but your remote has to indicate it supports incremental update


you MUST call ok or error exactly once.


that’s really all.


That blog article is using the old network protocol


(which should still work)


but ignore my earlier link…you don’t want middleware cause you’re not doing networking


FulcroRemoteI is the newer protocol

Joe Lane23:02:07

Below is my :auth remote

(defn ^:export init []
  (reset! SPA (fc/new-fulcro-client
                :started-callback (fn [app]
                                    ;(initial-load app)
                ;; This ensures your client can talk to a CSRF-protected server.
                ;; See middleware.clj to see how the token is embedded into the HTML
                :networking {:auth   (cognito/cognito-auth
                                       {:user-pool-id "us-east-1_somePool"
                                        :client-id    "SomeClientId"
                                        :region       "us-east-1"})
                             :remote (net/fulcro-http-remote
                                       {:url "/api"})}))

In the cognito ns
defn cognito-auth
  [{:keys [user-pool-id client-id region]}]
  (let [user-pool (user-pool user-pool-id client-id)]
    (map->CognitoAuth {:current-state (atom {::user-pool user-pool
                                             ::user-pool-id user-pool-id
                                             ::client-id client-id})
                       :parser        (prim/parser {:read cognito-read :mutate cognito-mutate})}))
And i have a record called CognitoAuth, what protocol does this need to implement. Ahh you just answered it.

Joe Lane23:02:34

I was getting a browser exception saying Invalid network

Joe Lane23:02:56

and I believe its due to not implementing the FulcroRemoteI protocol.


(transmit [this { [:edn :ok-handler :error-handler :progress-handler] :as raw-request}]

Joe Lane23:02:24

Thank you so much, I think I see it now.


doc strings in protocol should help

Joe Lane23:02:40

I was terrified I was going down the wrong path with remotes.


sorry, the docs could be better on that

Joe Lane23:02:01

I think the comment above the FulcroRemoteI was confusing me earlier on. It reads, ; Newer protocol that should be used for new networking remotes., which made me think since I wasn’t making a networking remote I was going in the wrong direction. Thought I didn’t need to implement that protocol, then hit this wall.


sry for the frustration

Joe Lane23:02:05

No worries, I really appreciate your help. Thank you.


@joe.lane tip: If you call ok handler with an ident-join query, then you can target a table entry, if that helps.

{:transaction [{[:user/id 3] [:user/name]}]
 :body {[:user/id 3] {:user/name "Bob"}}}


that kind of thing


the biggest “sticking point” will be when you want to use a component query, since that will likely create dep loops.


in that case, you should probably pass the UI classes as args to the network on creation


or something of the sort


{:transaction [{:top-key (prim/get-query User)}]
 :body {:top-key {:user/id 1 :user/name "Joe"}}}

Joe Lane23:02:21

Oh interesting. I’m still working on wrapping my head entirely around the basics of the framework but I’m sure this will come in handy sooner than I think.


So, Fulcro treats all data on equal footing: incoming data is a tree. Tree is merged using a query from components (that have idents) which is how normalization works.


initial db state is just this applied with root query to initial tree


mutations can “bit twiddle” things in, but in general you want to deal with graphs of data (usu trees on input) where the component-based (sub) query is responsible for organizing it into you db


From there, refresh is based on you saying “these props changed” (a follow-on read in a top-level mutation). Fuclro keeps an index of props on-screen, so it can find the things to refresh from there


that is the pattern…pretty much everywhere…it’s easy to get lost in the weeds, but it really is quite simple

Joe Lane23:02:46

I’m going to read through the above a few times, but I found this and think it will be a decent reference for other remotes going forward. I’m probably also going to go back through some of your older youtube videos like this one;t=2s


yep. The concepts in that white board video are still pretty much spot on. The references to Om Next internals no longer matter, and I say the wrong library name a lot, but other than that it is still correct. There are additional features at this point, but all of that is still really useful to know.