This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2019-02-19
Channels
- # announcements (1)
- # aws (9)
- # beginners (136)
- # boot (4)
- # cider (11)
- # clara (36)
- # clojure (337)
- # clojure-europe (5)
- # clojure-italy (3)
- # clojure-nl (2)
- # clojure-spec (9)
- # clojure-uk (112)
- # clojured (7)
- # clojurescript (93)
- # core-async (2)
- # cursive (30)
- # datomic (6)
- # duct (4)
- # emacs (2)
- # figwheel (1)
- # figwheel-main (7)
- # fulcro (153)
- # kaocha (1)
- # off-topic (12)
- # om-next (1)
- # pedestal (58)
- # planck (6)
- # re-frame (15)
- # reitit (11)
- # shadow-cljs (113)
- # spacemacs (1)
- # specter (3)
- # vim (8)
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:
(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
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
@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@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
hookI 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 https://github.com/fulcrologic/fulcro-incubator/issues/28. 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?
:ui.sub-form/channel]
:ident [:ui.sub-form/id] ;; what will fill the ID there?
:initial-state {:channel "#demo"}
:form-fields #{:channel}}
(dom/div
(dom/h3 "PubSub Form")
(dom/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
fn
form lets you use props from arg list to do it however you want, including just sending a constant ident
@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 dataico.ui.company.details/CompanyDetails'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 changein this case the instance of CompanyDetails
calling (my-mutations this)
and the implementation calls (prim/get-ident this)
yup, sure
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
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.
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
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
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
My code originally was tangled up in mutations which I figured wasn’t the right place since it isn’t a reproducible transaction
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.
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?
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
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
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?
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.
e.g. you could say “merge [:x] Using body {:x 1}” and you’ll get :x 1 in the root of your app db
update accepts the same thing, but can be called more than once, but your remote has to indicate it supports incremental update
but ignore my earlier link…you don’t want middleware cause you’re not doing networking
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"})}))
(start))
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. (transmit [this {::fulcro.client.network/keys [:edn :ok-handler :error-handler :progress-handler] :as raw-request}]
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.
@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"}}}
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
{:transaction [{:top-key (prim/get-query User)}]
:body {:top-key {:user/id 1 :user/name "Joe"}}}
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.
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
I’m going to read through the above a few times, but I found this https://github.com/levitanong/fulcro-rest-remote/blob/master/src/main/fulcro-rest-remote/core.cljc 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 https://www.youtube.com/watch?v=mT4jJHf929Q&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.