Fork me on GitHub

@devurandom if I understand where you are trying to get to, this may be interesting ...


WARNING: that FAQ entry does recommend not continuing in the direction you are leaning


Are there any guidelines on how one can break up bigger front-ends in re-frame? Right now I have done my own but I would like to see how others do it.


i break mine into feature-specific events, entity-based events, and (occasionally) feature-spanning events that coordinate


“change this state in feature foo” vs “update the global entity foo” vs “do this in foo, then after do this in bar and baz”


I don't want a events.cljs with 2k lines of code 🙂 (or more). Splitted it up already.


Thank you I will have a closer look @mikethompson ✌️


Generally speaking you divide up by functional unit (which is normally page/panel)


One directory for each


Within that directory, all events views etc assoaicted with that functional unit: ie. events.cljs,`view.cljs` subs.cljs etc


perfect, I will do some refactoring parrot


If I have a sub for coll (for the list-all-in-coll page) and one for (get coll id) (for the show-item-details page), I'm doing it roughly as intended, right?


@mikethompson Thanks for the links!


@mikethompson Assuming I have a (reg-sub ::my-coll), would it indeed be stupid to have a:

  ; Triggered when the subscription is actually realised, i.e. the first time someone subscribes
  (fn [cofx [_ sub]]
    (if-let [handler (get sub-realisation-handlers sub)]
      (handler cofx sub))))
(def sub-realisation-handlers
  {::my-coll my-coll-sub-realisation-handler})
(defn my-coll-sub-realisation-handler [cofx sub]
  "Fill my-coll with data via a re-graph subscription"
  {::re-graph/subscribe ...})


That way my pages would just care about their data input, i.e. their subscriptions, instead of having to care about triggering the process that makes this data arrive in the subscription.


And it's still events causing side-effects. With the small change that now re-frame's realisation of the subscription (previously an implementation detail) also dispatches events.


I use reg-sub-raw to achieve that @devurandom


@oliy You (dispatch [::my-coll-queried]) from within the ::my-coll raw subscription? And then the event handler checks whether the re-graph subscription already exists and creates it if not?


Yes, sort of. It uses the lifecycle of the subscription to subscribe and unsubscribe in re-graph


I can show you some code in about an hour when I'm at my computer


On the topic of Eric's post on react vs re-frame: "That said, you don't have to do it using Events and the Database at all. You can simply create a Reagent Atom that always has the current value of document.body.scrollTop." << Is that actually re-framy? Having data outside of :db that my view uses?


I sometimes do that for very local state that I don't care about reproducing e.g. does a text input have focus or not


@oliy That would be awesome!


i hope it makes sense - a pattern for a subscription that manages its own data source, in both re-graph and martian flavours


that way when anyone needs some data, you just subscribe to the data source, you don't have to care where it comes from or how to initialise it or how to clean it up afterwards


it relies on re-frame's deduplication of subscriptions


Looks awesome. Why is this not included in re-graph?


(re-graph/reg-sub ::raw-things "{ things { id } }"), which would automatically create the re-frame/reg-sub-raw and the re-frame/reg-event-db from your paste.


There are many different use cases, it would only make it easier for one, but maybe more confusing for others. For example I sometimes use a subscription just to get one message, which might take time like

  (fn [cofx [_ {:keys [data errors] :as payload}]]
    {:db       (update (:db cofx) :transfer-data #(merge % (:money_transfer data)))
     :dispatch [::re-graph/unsubscribe (keyword (str "transfer-" (:uuid (:money_transfer data))))]}))


@gklijs why not use re-graph/query for that?


that is exactly what it is for


Since it needs to be processed, I could keep querying till it's there.


ah i see


@urzds i'm not sure how reusable that would be as part of re-graph


even in my own codebases i tend not to have a generic version of that, because each one tends to be slightly different


It's a bit of cqrs-like demo, I pass the command to transfer some ammount to the graphQl server, it then gets processed by other applications with kafka messages, and eventually (but in practice really fast) either a failed or succeed messages will be read by the graphQl server, witch, because of the subscription, will pass it to the client.


(the arguments vector is important as that is part of what governs re-frame's deduplication)


Would be really great if I could build something like that for actual production use, this was kind of a poc, on my own initiative, but could not convince the decision makers.


Is there a way to "queue" a subscription? My problem is that initially when the app starts I don't know the re-graph configuration. It arrives later and then a ::re-graph/init is dispatched, but in the meantime I might have tried to issue some subscriptions already, which would fail, because there was no re-graph config yet.


@oliy Do you have a page for re-graph?


no don't think so, i got a PR recently for martian but until then i hadn't heard of it


Try it, it's awesome. You can also have prose text, like re-frame's documentation, alongside the API docs. Very useful.


FWIW cljdoc builds docs automatically so you can just look up re-graph docs via the search on the home page... here's the re-graph docs:


If you want I can open a PR adding a badge.


there's no current way to queue a subscription because you need to init re-graph first so it knows where and how to queue things


but you're the second person to ask that recently (unless you're the one who raised it on github the other day) so it seems like it could be a required feature


in my apps authing the user, initing re-graph etc are all things that are done before the views are mounted, and it's the views that subscribe to the subs that want to fetch data


So you call re-graph/init directly, instead of dispatching an event?


so that takes care of that race condition


if you want you can raise an issue on github with an example of how you would want to use it


I would add an optional argument to ::re-graph/subscribe: an options map that contains a key :queue?, and then add the following to the handler function, before :else:

(:queue? options)
{:db (update-in db [:subscription-queue] conj [subscription-id query variables callback-event])}


And in the ::re-graph/init handler check the :subscription-queue and execute all those subscriptions after initialising the data structures.


i'm guessing one call to init and then a later call to connect


I'm not sure I understand the last part you mentioned. Call init, call connect?


I'm trying to understand ::re-graph/subscribe: When the websocket is not ready, but it exists, you immediately queue the result event without actually subscribing to anything or fetching any data?


Maybe we are using a wrong approach, would be interesting to hear your opinion on this: Because in the frontend CLJS code we do not know where the GraphQL backend will be, we load a config file from the frontend web server (we can make that point to the correct backend server for every deployment of our generated-JS frontend code). Hence we cannot ::re-graph/init right after the application loads in the browser, because we first have to wait for the config, and only as a result of the config arriving we will initialise re-graph. Before my idea of doing the re-graph subscriptions automatically from re-frame subscriptions, we would also dispatch several ::re-graph/subscribe right after dispatching ::re-graph/init.


We could workaround this by ourselves if ::re-graph/subscribe had an on-failure event in addition to the on-success event (`callback-event` in the ::re-graph/subscribe code). This event would have to be dispatched in the :else branch of ::re-graph/subscribe.


i would organise it like this

(defn app-init []
  (go (let [ws-endpoint (<! (fetch-config))]
               (re-graph/init ws-endpoint)
               (reagent/mount-component [root-component]))))


this will block your app from rendering until it knows where the backend is, of course, but for the apps i do that is ok


We use honeybadger also for Clojurescript, and I noticed that for some reason all re-frame errors triggered by something like (rf/dispatch [::not-found]) don't get logged

re-frame: no :event handler registered for: :event-handler-not-there
(anonymous) @ core.cljs:3884
(anonymous) @ core.cljs:3879
(anonymous) @ core.cljs:3873
(anonymous) @ core.cljs:3867
(anonymous) @ core.cljs:3861
(anonymous) @ core.cljs:3896
cljs$core$apply @ core.cljs:3887
(anonymous) @ loggers.cljc?rel=1541758287855:38
re_frame$loggers$console @ loggers.cljc?rel=1541758287855:35
(anonymous) @ registrar.cljc?rel=1541758288685:31
re_frame$registrar$get_handler @ registrar.cljc?rel=1541758288685:18
re_frame$events$handle @ events.cljc?rel=1541758289590:57
(anonymous) @ router.cljc?rel=1541758289832:179
re_frame$router$_process_1st_event_in_queue @ router.cljc?rel=1541758289832:84
(anonymous) @ router.cljc?rel=1541758289832:198
re_frame$router$_run_queue @ router.cljc?rel=1541758289832:86
(anonymous) @ router.cljc?rel=1541758289832:146
(anonymous) @ router.cljc?rel=1541758289832:169
re_frame$router$_fsm_trigger @ router.cljc?rel=1541758289832:80
(anonymous) @ router.cljc?rel=1541758289832:187
channel.port1.onmessage @ nexttick.js:211


we hook to window.onerror if I understand correctly


any idea how to catch these errors as well?


@andrea.crotti See the FAQs #7 for a description of how to hook that re-frame: no :event handler registered for: :event-handler-not-there error