:req nil?
?
Whoops. I have correct the example and the result is the same.
The second explain results. It is as if guardrails is generating some data as a kv-pair when it should be generating it as a map (of course, this is only a guess)
No. Make latest when you insert the data in your client DB. How else could it be inserted to a unique place?
Part 5 of G.F. is uploading now.
Looks like 30 mins…
The videos are simply awesome! Thanks so much Tony for taking the time to teach us and help us to really grok Fulcro. P.S. it seems Part 5 is not yet added to the G.F. playlist.
ooops
OK, this is probably super obvious to the point that I cannot figure it out. Looking at https://github.com/fulcrologic/fulcro-rad-demo/blob/develop/src/shared/com/example/model/authorization.clj, there is a function that has an env
as a parameter. If I wanted to run the login!
function in REPL, assuming the Fulcro RAD demo server has been started, what would I use for env
parameter?
The env is built by the plugins of the pathom parser. So, typically you’d write a login mutation, which would then be auto-passed an env when invoked through pathom.
Otherwise, env
is just a map, and you largely define what is in it. If the function in question doesn’t use much (or anything) from the env, then just make a map that has what it needs.
In this case, it expects env
to have the Ring request which it expects has a session {:ring/request {:session {…}}
.
(`get-login-info`, I think, uses the database connections out of env as well)
Under https://blog.jakubholy.net/2020/troubleshooting-fulcro/#_backend_pathom look at 1.d
https://github.com/holyjak/fulcro-troubleshooting has been updated to check also idents are correct and that you are not missing data for joins (ie. your children) - typically because you loaded data into the wrong place or forgot initial state for a comp. that has only link queries. Please see the docs and give it a try! 🙏
Ok, this does shed the light on things. Probably quick and easy prototyping is to compose a map and feed it to the fn.
Side effects:
I’m following the Login example from the fulcro template and so far everything makes sense. I’m still getting used to a lot of the framework indirection: eg. calling uism/trigger-remote-mutation
understanding m/returning will send data to the component you specify and you’ll handle that in pre-merge is a little gnarly for me.
Anyways, I’m using a token based flow, and I’d like to save the token to local storage so I can load it (or the refresh token), when the app starts up again. What would be the idiomatic way to achieve that?
My current solution is to sneak in a (token-store/save-token token)
call in the pre-merge call of the m/returning
component, but I’m not sure about the lifecycle and implications of this
(defsc Auth
"Auth representation. Used primarily for server queries.
On-screen representation happens in Login component."
[_ _]
{;; The fields we're querying in our local database
:query [:user/id :token]
:ident (fn [] [:component/id :auth])
:pre-merge (fn [{:keys [data-tree]}]
;; Save the token if provided
(when (some? (:token data-tree))
(token-store/save-token (:token data-tree)))
(merge {:user/id ""}
data-tree))
;; Default initial state
:initial-state {}})
Okay, I'll go down that path.
Aliasing was definitely a cleaner solution for the other problem I was working on. Thank you! It's all coming together.
A quick sidenote:
Asynchronicity in state machines:
On initialization I load the token from local storage, which is an asynchronous action. In order to make this work, I use the application atom to trigger the complete event once the token is loaded. It works fine. The difference is that I do uism/trigger! @app :event/complete
As opposed to having a returnable uism/async
function where you can provide it with a success and error event and have the handler return this normally.
I'm guessing the two solutions would be what I did, or abstracting local storage out into a remote and use trigger-remote-mutation instead?
no, that’s not what you want. You’re triggering a remote mutation or load aren’t you? Those functions have options for triggering the event. The atom itself is useful for network middleware to avoid cycles…I would not use it anywhere else in this problem.
So the question is: Is there a way to do async actions in a state machine outside of using a remote? It is on my TODO to figure out how to wrap local storage in a remote interface, I just haven’t prioritized it so far
(thank you for clarifying all this by the way!)
ah, yes, localstorage would normally be a remote in the Fulcro model
so you mean you want an event after localstorage completes….It is perfectly fine to do (async-call-that-takes-callback (fn [] (uism/trigger APP …)))
I thought you meant you were adding a watch to the atom or something…I just misunderstood what you were saying
I basically consider the internals of a UISM event handler to be at a lower abstraction level. Moving I/O side-effects to a remote is more “purely clean”, but is a level of code hygiene that is sometimes overkill with such a localized concern IMO. For this use-case, I’d probably do what you did.
Perfect. That’s exactly what I figured. So far, all I’m writing is the refresh-token. It felt overkill to create an entire remote for that. So that’s exactly what I did. Load the token and use the async callback to trigger an event. Thank you for validating!
Just to clarify : m/returning will not "send data to the component you specify". It tells Fulcro that the mutation returns a data and what data that is (described by the given component, because, remember, components describe your data model) so that it knows where to put it (based on the ident). Normally I would do storing of the token in ok-action of a mutation or in another mutation this triggers. But I haven't looked at your example so I might be off the mark here. Pre-merge certainly feels as the wrong place for side-effects. Those belong in mutations.
Thank you for the pointer. Digging for the event-data right now. Another thing: Is there a way to load app state side band? I’m asking because I need to add the token to my api request. Right now i’m loading it from an atom, but I’m thinking it might make more sense to load it from the client db where it’s saved anyways:
(defn wrap-with-token [req]
;; Could I load the token from the Auth
;; component instead?
(if-let [token @token-store]
(assoc-in req [:headers "Authorization"] token)
req))
I personally do not use pre-merge a lot, and that particular use of pre-merge is not great. I’d prefer that side-effect be in the UISM. Here’s what I’d probably do (in a UISM): On UISM start (which is app start, I’m guessing), I’d pull the token from localstorage (if present) and put it in app state. I’d use an alias for the Auth’s field that represents that token, so it is just an assoc-alias call. On the state that tries to log in you can tell it what events to fire as a result of a mutation. So, you’d trigger the remote mutation to do the login, with a returning of Auth…but you don’t need pre-merge because you already have the alias to where the token is in state, it’s just the aliased value. So, the event handler in UISM can just look to see if it got a valid token. Some other alias might track where you store your login error, etc.
on your second question: your middleware wrapper could use the app to pull state:
(defn wrap-token [app] (fn [req] ...))
That typically creates a circ ref, so this is where an app atom could be handy to break the cycle (app uses remote uses middleware uses app)
ohhh… Interesting… I’ll try that out @tony.kay. The aliased way. For pulling out of env I created this helper:
(defn env->mutation-result
[env mutation]
(get-in env [::uism/event-data ::uism/mutation-result :body mutation]))
yeah, don’t do that….it’s going into state already
I do have an app atom. Which function would allow me to pull it from the app atom? (The good thing about the book is that it contains almost everything I need. The bad thing is finding out where it is can be a little tricky)