This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-08-17
Channels
- # announcements (7)
- # babashka (24)
- # beginners (11)
- # boot (16)
- # calva (46)
- # cider (5)
- # clara (3)
- # clj-kondo (2)
- # cljfx (5)
- # clojure (122)
- # clojure-brasil (26)
- # clojure-dev (20)
- # clojure-europe (20)
- # clojure-germany (1)
- # clojure-nl (1)
- # clojure-norway (54)
- # clojure-uk (2)
- # clojurescript (6)
- # core-matrix (23)
- # datomic (85)
- # graalvm (1)
- # honeysql (9)
- # hyperfiddle (31)
- # lsp (3)
- # malli (9)
- # nbb (2)
- # off-topic (15)
- # pathom (15)
- # pedestal (4)
- # polylith (5)
- # re-frame (5)
- # reitit (9)
- # releases (2)
- # shadow-cljs (63)
- # specter (4)
- # xtdb (7)
I'm trying to use reitit as a frontend router using controllers per https://github.com/metosin/reitit/blob/master/doc/frontend/controllers.md, but I notice it behaves differently than I'd like if I push state from inside the controller itself. Is anyone not using controllers at all, and if so, how are you managing "on mount/off mount" effects of pages?
Not sure how helpful it is, but I'm using kee-frame with reitit and it seems to be working just fine.
I’m using controllers on some routes and not on others. When I am using controllers it’s typically something like this where I fire off a re-frame event to take some action or to initialize some values in re-frame’s app-db (if you’re not using re-frame then it might instead be some globally-defined state atom):
(def routes
["/users" {:controllers [{:parameters {:path [:client-key]}
:start (fn [{{:keys [client-key]} :path}]
(rf/dispatch [::fetch-users client-key]))}]}])
On some pages with forms that have their own localized state, I don’t want to clog up app-db with flags and stuff that only belong to the form, so I don’t use controllers, but instead use https://cljdoc.org/d/reagent/reagent/1.1.0/doc/tutorials/creating-reagent-components#appendix-b---with-let-macro`with-let`https://cljdoc.org/d/reagent/reagent/1.1.0/doc/tutorials/creating-reagent-components#appendix-b---with-let-macro, which is just sugar for a Form-2 component with some additional life-cycle stuff (so maybe that makes it sugar for a Form-3?):
(defn page [client-key]
(r/with-let [state (r/atom {:editing? false})
toggle-editing (fn [] (swap! state update :editing? not))
detect-escape (fn [e] ...logic to detect ESC key press...)
_ (.addEventListener js/document "keydown" detect-escape)]
(let [editing? (:editing? @state)]
[:form
...alter view based on `editing?`
...button to toggle editing
...])
(finally
(.removeEventListener js/document "keydown" detect-escape))
You may not care if you are not using re-frame, but if I do have a situation where re-frame events need to do something with the local state
atom, I close around it with a function defined inside page
and send that function as a parameter to the event (for example, clearing an error).The issue I'm running into is with having a controller that redirects, say, if session is not present
["secret"
{:name ::secret
:controllers [{:start #(wrap-restricted
(fn []
(log-fn "start" "secret controller")
(rum/mount (secret-page) app-div)))
:stop #(log-fn "stop" "secret controller")}]}]
wrap-restricted
will redirect to login if not already logged in, but this causes an issue with the way Reitit recommends keeping track of route matches.
(defn ^:dev/after-load start-router []
(rfe/start!
(router)
(fn [new-match]
(swap! match (fn [old-match]
(let [result (when new-match
(assoc new-match :controllers (rfc/apply-controllers (:controllers old-match) new-match)))]
result))))
{:use-fragment false}))
If you redirect from a controller, you will end up at /login
, but the swapping logic here will first swap the match atom to /login
and then swap it to where we originall intended to go, /secret
. It seems to do it in a weird order, so my match state is completely messed up. So I can't use controllers for redirects, for some reason.What exactly is wrap-restricted
? I guess it may not matter because, you are right, controllers get called during rfc/apply-controllers
and that is not a good place to be then calling rfe/push-state
or rfe/replace-state
and triggering a new match. Could you have wrap-restricted
redirect on a slightly delayed basis? This isn’t an issue with re-frame because events are dispatched on the next event loop and so we avoid the recursion issue you are dealing with.
Maybe in wrap-restricted
you can do (js/setTimeout #(rfe/push-state name))
and that will queue it up to execute after apply-controllers
finishes.
Yes that would probably work. In the future for myself, how were you aware this was bad practice? It can be a little hard between the 20 specialty libraries to make a functioning web app to know the idiosyncrasies of all of them. I would've thought a push state inside of a controller would've been fine because the first controller would execute and then the second one would execute later
Yeah essentially wrap-restricted just checks session and pushes state if it's not there
There is no one answer to your question about the future. I guess you just learn as you go along. And as things don’t work. 🙂 And as you ask questions here on slack. In this particular case, it’s mostly reading the reitit docs and source code on what apply-controllers and push-state do and learning that push-state calls the function you provided to start!
as the on-navigate
argument which calls apply-controllers
which, if you call push-state from inside one of those controllers, gets you into a strange recursive situation. Report back if using setTimeout worked or not. I remember having another situation where I needed to do that, but since I use re-frame, I mostly don’t have to worry about it because re-frame asynchronously queues up events.