Fork me on GitHub
Romit Gandhi06:11:00

Hello, I'm using re-frame with shadow-cljs and for authentication using JWT. In the login/registration API I'm getting JWT along with user info so, I'm storing it in app-db as user-secret and user. Now, when I want to call any API I'm passing that token in headers so, it will verify it so, that's not an issue. But I also want to check whether user and user-secret is present in app-db when user navigate the route (Not in all route like I don't want to check that in login/registration). What is the best way of doing it? Currently, which routes I have to check I pass controllers and start & inside that dispatching an event. Eg:

["/book/add" {:name :add-book
              :view book/add-book
              :controllers [{:start (fn [_]
                                      (rf/dispatch [:check-authentication]))}]}]
And check authentication is like below.
  (fn [{:keys [db]}]
    (if (empty? (:user db))
      {:navigate! [:login]})))
Any best alternative for doing this ? Thanks.


If the only thing that actually does the navigation itself is the :navigate! effect, then the bare minimum solution would be to implement a global interceptor, check there whether :navigate! is present, and replace its argument with [:login] if there's no user information. If you can also navigate via regular links, then see your navigation/routing library's documentation - perhaps, it will allow you to add a global controller that would be triggered for all the routes. If not, then just creates routes as you do now, and simply add the same controller to every route with mapv or so.

Quentin Le Guennec14:11:16

Does re-frame run subscription handlers synchronously?


Depends on what you mean. They are not run concurrently, but there's no guarantee about their order.

Quentin Le Guennec14:11:07

@U2FRKM4TW thanks for your answer. We have a sub handler that takes way too long time that we'd expect. Our guess is that the browser doesn't give the cpu control to that subscription handler often enough. Does that make sense?

Quentin Le Guennec14:11:50

eg, re-frame runs 10% of the handler, saves the execution stack, gives the control back to other parts, then 10% again, etc


Re-frame itself doesn't do it. But it can happen if your sub uses promises or async JS functions, although that would be strange - you would receive a promise as a result of that sub.

Quentin Le Guennec15:11:10

I see, thank you. We don't have anything like these in our codebase.


Are you sure you don't have a CPU hog somewhere that simply prevents your page from re-rendering?

Quentin Le Guennec15:11:58

you mean within the browser?

Quentin Le Guennec15:11:26

there are dispatchs happening, I think 2


That doesn't really answer the question. During a CPU hog you can't do anything. If you see that your page is not responsive, I would profile it using your browser's devtools and see what's going on. Apart from that, hard to say anything without having the actual code.

Quentin Le Guennec15:11:51

the subscription handler itself is doing: 1. check the local cache (app-db) if the requested entity is present 2. if not, fetch the data (dispatch) and display a loader 3. if the data is present, reconstruct (denormalize) the requested entity (a graph with 20k+ relationships)

Quentin Le Guennec15:11:15

the ajax request itself takes ~300 ms to execute, but the whole process takes around 5 to 7 seconds

Quentin Le Guennec16:11:10

the page seems responsive, but the page is just a loader, so we can't really tell


A subscription handler must not side-effect.


All side-effects must be done in effects.

Quentin Le Guennec16:11:43

it's what's recommended in the re-frame docs though, "how to load external data", the subscription handler runs a dispatch


Note the very beginning of that document.

Quentin Le Guennec16:11:01

it shouldn't really be an issue, though?

Quentin Le Guennec16:11:36

it's very convenient and we'd like to avoid having to change that pattern


No clue, I have no idea what you're actually doing. And I find it rather hard to reason about impure subscription and even handlers most of the time. Have you tried using a global interceptor to conditionally load the required data?

Quentin Le Guennec16:11:43

The pattern in question is: subscribe to data -> data not present, dispatch the http request (in the sub handler)

Quentin Le Guennec16:11:10

I'll look into global interceptors, not sure it'll help, I thought it's about event handlers, not subscriptions


Right. You never subscribe to some data out of the blue. It always happens as a consequence of some events. So you can move data loading to a global interceptor that would simply watch for those events, either by event IDs (and if you know them for sure, it might be better to use a regular interceptor with just those events) or by some marker. An example - you need to load data for a table when that table is first shown. But the table is visible only when the :table-visible? key in the app-db is set. So, you monitor that key in a global interceptor and load the data when it becomes not false-y.

Quentin Le Guennec16:11:24

I'd be intercepting a dispatch, right? In your example, the dispatch in question would be [:open-table]?

Quentin Le Guennec16:11:30

If so, we can't do that, that would imply a heavy refactoring. All of our app is dependant on the pattern shown on the doc I sent earlier.


You'd be intercepting an event. Which might not come from a dispatch, but usually they do come from there, yes.

Quentin Le Guennec17:11:19

From where would it come from, if not an dispatch?


You can modify the event queue. But I've never seen such a thing being done, and I can't think of any reason to do that.

Quentin Le Guennec17:11:28

I see. Then I guess we'll keep the "dispatch in the subscription handler" pattern. This shouldn't cause any performance issue, anyway.


One thing a global interceptor might help with is just seeing where those 5 to 7 seconds are going. I suspect a race condition in which the data stars just happen to align eventually.


Does re-frame guarantee that metadata on a dispatched event will be passed to any downstream handler function (e.g., event handler, post event callback)?


There are no explicit guarantees, but if I recall the implementation correctly, it should work. But any interceptor could easily break it.


It does seem to work right now. If re-frame doesn’t make any guarantees there then it seems a bit sketchy to rely on.