re-frame

https://github.com/Day8/re-frame/blob/master/docs/README.md https://github.com/Day8/re-frame/blob/master/docs/External-Resources.md
rberger 2020-07-26T01:50:46.085300Z

Its pretty easy to use react-bootstrap especialy if you use shadow-cljs to load the react and react-bootstrap npm libraries via the package.json. Something along the lines of:

(ns myapp.react-bootstrap
  (:require
   [reagent.core :as r]
   ["react-bootstrap" :as rs]))

(defn adapt [component]
  (try
    (r/adapt-react-class component)
    (catch js/Object e
      (prn e.message))))

(def accordion        (u/adapt rs/Accordion))
(def accordion-toggle (u/adapt rs/Accordion.Toggle))
(def accordion-collapse (u/adapt rs/Accordion.Collapse))
(def alert (u/adapt rs/Alert))
...  Continue with all the react-bootstrap components you want to use

Then use them in your app
(ns myapp.view
  (:require
   [reagent.core :as r]
   [myapp.react-bootstrap :as rb]))

(defn some-alert []
  [rb/alert {:variant "warning"} "It's a trap!"])
You can also use the react-bootstrap compoents inline with the :> similar to how they used react-markdown in https://github.com/reagent-project/reagent/blob/master/examples/react-mde/src/example/core.cljs

genekim 2020-07-26T19:23:32.087400Z

I’m wondering if anyone else has the problem of sprawling events.cljs files, and a flow of events that’s difficult to keep track of and keep in one’s head. I have an SPA which I use for Trello card management, which I’ve built up over 3 years — it’s actually two SPAs, one intended for use in desktop browser (supporting keyboard accelerators) and the other in mobile interface . The two SPAs share the same db.cljs and events.cljs. The total is about about 3K LoC, of which the largest file is the events.cljs, which is 1.2K LoC. There are 105 events, and I often have trouble keeping track of them — they can be as high level as “load list of cards” or “load board,” to “set active card position” to “move card to list” to “set browser focus on description text field” — to internal private events: “callback-load-list,” “embed-youtube-urls.” I’m ridiculously proud of myself for writing a Clojure program to read the events.cljs file and extract the “call graph,” pulling out the re-frame/dispatch calls and the reg-event-fx dispatch maps. (OMG. This is my first visceral experience of awe at the value of homoiconicity!) I’ve learned that I have at least 65 events calling other events, putatively making the remainder leaf events, which don’t dispatch any other events. Even before this exercise, it occurred to me that I don’t know what the ideal event flow and shape of the call graph should even look like! What does one look like that allows for re-use, versus what feels like spaghetti right now? Are there naming schemes for the events to support better organization? And what should the naming scheme be for events, to impose an order/organization to it? Many thanks in advance!

genekim 2020-07-26T19:27:00.089600Z

FWIW, here’s the “call graph” that I extracted — any quick reactions to it or critiques of anything I’m doing wrong? (Not posting as a reply, to allow easier, wide reading.) Each vector is the name of the event, followed by all the other events that it dispatches.) (I’ll post the repo in the next couple of days, despite all its flaws/inadequacies. I was playing around with rendering it with graphviz, or maybe vega arc-diagrams, but finding the text display surprisingly compact and useful.)

genekim 2020-07-26T19:27:00.089700Z

([:graph/load-initial-state-success :graph/server-load-hotkeys]
 [:graph/select-board :graph/save-and-clear-searchbox]
 [:graph/select-board :graph/load-board-lists]
 [:graph/select-board :graph/select-board-next-leftpane-state]
 [:graph/callback-load-board-lists :graph/load-list-cards]
 [:graph/callback-load-board-lists :graph/load-list-card-counts]
 [:graph/load-list-cards :graph/reset-card]
 [:graph/load-list-cards :graph/generate-materialized-cards]
 [:graph/reload-all-lists-and-cards :graph/load-list-cards]
 [:graph/reload-all-lists-and-cards :graph/load-board-lists]
 [:graph/reload-all-lists-and-cards :graph/load-list-card-counts]
 [:graph/change-list-sort-mode :graph/generate-materialized-cards]
 [:graph/generate-materialized-cards :graph/iphone-materialize-view]
 [:graph/load-card-comments-attachments :graph/reset-card]
 [:graph/load-card-comments-attachments :graph/load-card-comments]
 [:graph/load-card-comments-attachments :graph/load-card-attachments]
 [:graph/iphone-materialize-view :graph/iphone-load-card-attachments]
 [:graph/select-list :graph/save-and-clear-searchbox]
 [:graph/select-list :graph/load-list-cards]
 [:graph/select-board-or-list :graph/save-and-clear-searchbox]
 [:graph/callback-load-card-comments :graph/rewrite-desc-twitter-call-oembed]
 [:graph/iphone-rewrite-desc-twitter-call-oembed-all-cards :graph/rewrite-desc-twitter-call-oembed]
 [:graph/rewrite-desc-twitter-call-oembed :graph/callback-handle-twitter-oembed]
 [:graph/next-card :graph/scroll-to-top]
 [:graph/previous-card :graph/scroll-to-top]
 [:graph/goto-top-card :graph/reset-card]
 [:graph/goto-top-card :graph/load-card-comments-attachments]
 [:graph/goto-bottom-card :graph/reset-card]
 [:graph/goto-bottom-card :graph/load-card-comments-attachments]
 [:graph/archive-card :graph/next-card]
 [:graph/callback-archive-card :graph/generate-materialized-cards]
 [:graph/callback-archive-card :graph/load-card-comments-attachments]
 [:graph/callback-archive-card :graph/load-list-card-counts]
 [:graph/left-pane-set-form-text :graph/generate-materialized-cards]
 [:graph/start-move-card :graph/focus-searchbox]
 [:graph/move-card-to-list nil]
 [:graph/move-card-to-list remove]
 [:graph/move-card-success :graph/reset-card]
 [:graph/move-card-success :graph/load-card-comments-attachments]
 [:graph/move-card-success :graph/generate-materialized-cards]
 [:graph/move-opt-number-key :graph/move-card-to-list]
 [:graph/opt-letter-key :graph/execute-hotkey-move]
 [:graph/move-current-card-to-top :graph/move-card-to-top-or-bottom-of-list]
 [:graph/move-current-card-to-bottom :graph/move-card-to-top-or-bottom-of-list]
 [:graph/move-card-to-top-or-bottom-of-list [:graph/move-card-to-list [dest-list-id board-id pos]]]
 [:graph/move-card-to-top-or-bottom-of-list if]
 [:graph/iphone-set-cardpos-for-move :graph/execute-hotkey-move]
 [:graph/iphone-set-cardpos-for-move :graph/iphone-move-card]
 [:graph/iphone-set-cardpos-for-opt-number-key :graph/move-opt-number-key]
 [:graph/iphone-set-cardpos-for-opt-number-key :graph/iphone-move-card]
 [:graph/edit-card-text :graph/focus-edit-card-name]
 [:graph/handle-keyboard-next-prev-list :graph/load-list-cards]
 [:graph/handle-keyboard-move-to-hotkey :graph/focus-hotkeyform]
 [:graph/handle-keyboard-set-move-hotkey :graph/focus-hotkeyform]
 [:graph/handle-keyboard-delete-move-hotkey :graph/focus-hotkeyform]
 [:graph/handle-keyboard-goto-hotkey :graph/focus-hotkeyform]
 [:graph/create-list-success :graph/load-board-lists]
 [:graph/submit-save-hotkey-form :graph/server-save-hotkeys]
 [:graph/submit-delete-hotkey-form :graph/server-save-hotkeys]
 [:graph/submit-goto-hotkey-form :graph/select-board]
 [:graph/submit-goto-hotkey-form :graph/load-list-cards]
 [:graph/execute-hotkey-move :graph/move-card-to-list]
 [:graph/handle-keyboard-repeat-last-command :graph/move-card-to-list]
 [:graph/route-goto-card-id :graph/scroll-to-top]
 [:graph/route-goto-card-id :graph/reset-card])

👍 1
👏 2
genekim 2020-07-27T14:48:40.091900Z

This has helped me zero in on where the complexity/messiness is — not surprisingly, it typically is around operations that have multiple asynchronous steps. Like this one:

[:graph/archive-card :graph/next-card]
 [:graph/callback-archive-card :graph/generate-materialized-cards]
 [:graph/callback-archive-card :graph/load-card-comments-attachments]
 [:graph/callback-archive-card :graph/load-list-card-counts]
I.e., to archive a card, it’s got to advance the view to the next card, call the backed to archive it, upon the callback, pessimistically update the list of card, load the next card contents/comments… I’m wondering aloud how better to do these multi-step sequence of events, where it’s more obvious what the steps are — documenting this way is good, but steps seem currently splattered across too many events, which linkages not obvious enough.

rberger 2020-07-28T05:42:40.095500Z

We ended up mostly breaking our event.cljs up into files under events that matched our views using namespaced keywords for the events. We kept the subs in a global subs file at least for now.

👍 2
genekim 2020-07-28T14:27:26.095900Z

Whoa! What a super, super idea!!! Thank you!!!

genekim 2020-07-28T14:39:03.096100Z

Thanks so much for the suggestion, @rberger. PS: as I’ve been studying what has been tripping me up in the events, one of them is the lack of “information hiding.” In the global db, I have the raw list of Trello cards, and a “materialized view”, where filters are applied and other things I don’t remember. I had some events inappropriately accessing the raw list, instead of the materialized list… I’m now pondering how to keep certain parts of code from seeing the raw card list, lest I make the same mistake again… (Maybe I nest certain elements in the global db into an area called, :private-do-not-look-or-touch` — as they are the “model,” not for the view?)

2020-07-29T05:34:04.104Z

I support the idea of namespaced events. I usually partition my app by views/usage with an additional general “components” folder for generic components. I think the challenge is to make a trade off between generic events and reasonability/locality of your logic. Moreover, you need the discipline to avoid calling events from other views, except the general one.

genekim 2020-07-29T16:07:48.119400Z

@neo2551 This sounds super interesting, as well — so to make sure I’m processing this all correctly: Option 1: @rberger suggests breaking up the events namespace, which I’m guessing are all required into the main events.cljs. This has the benefit of organizing the events, modularizing, information hiding, etc. Option2 : @neo2551 suggests going beyond just splitting up the events namespace, but actually creating separate dbs/events/subs, as per: http://day8.github.io/re-frame/App-Structure/ I think in my case, I’m going to try Option 1, because I’m not quite ready to distangle everything yet — but making separate piles of events is something I can manage. 🙂 thank you, all!

2020-07-29T18:18:42.121400Z

The advantage of having fully qualified namespace is also to know where they are defined. But good luck :)

🙏 1
rberger 2020-07-29T18:31:16.121700Z

@genekim We don’t require the events/*even.cljs in the top level / global events.cljs. We just require them as needed in the usually parallel views/*view.cljs files. Our current project did evolve from a global events.cljs to this model and I susepct we may eventually evolve to something like what @neo2551 suggests. But evolution is good. Learn as you go. One thing good about having them all in a global events though, is no worry about circular dependencies. It hasn’t bit us hard, but it has bit us a few times. Usually a bit of refactoring/rethinking fixes that.

🙏 1
Jose Varela 2020-07-30T00:35:26.123900Z

I’ve seen this problem in Redux too. I think this is a problem with event-driven architectures. Maybe these could help make sense of the dependencies: 1. Logging: you see what event happens before/after another event. Even while running tests or playing around with the app (checkout https://github.com/day8/re-frame-10x) 2. Make the other end visible: similar to what you did but make subscriptions visible - what view listens to what change? (view -> sub -> event) could complement your event list

uosl 2020-08-01T10:43:30.153Z

I second using re-frame-10x. Our app has an event call graph at least as convoluted as yours, but I find it very manageable with 10x. Instead of trying to discern the flow of events or the contents of app-db from the code, I simply run the application and observe its behavior using 10x. To make event flows more explicit in the code, you can use https://github.com/day8/re-frame-async-flow-fx but it has a lot of overhead so it's only recommended for the initial boot of your app, after which it gets disabled. The call graph program you created sounds very cool! I think 10x could be made even more powerful if we had a visual representation of that (I know there's a similar issue for visualizing subscription graphs).

Jose Varela 2020-08-01T20:20:27.154100Z

Love the syntax https://github.com/day8/re-frame-async-flow-fx#flow-as-data @regen This makes event-chains easy to read. The docs say the main purpose is for sequential app-boot logic. Why isn’t this flow-syntax used for chaining events in any stage of the app’s life-cycle? My hunch is it’s too much overhead for chaining 1-2 events together, and if you have long chains you’re probably doing something wrong by structuring your flow of data that way?

uosl 2020-08-02T09:44:24.154600Z

@joservarelaf I think there are scenarios where you are forced to structure long event chains of HTTP requests during an app's lifecycle, maybe because of a badly designed backend API. The overhead of async-flow-fx comes from it using forward-events-fx to forward all events to the async-flow event handler, in effect causing everything to be dispatched double. I wonder if you can make something similar using an interceptor instead, and then injecting that into all the participating event handlers. I think there are some places in our app that could benefit from this, as currently we're manually checking some part of app-db in every event handler in the chain, to be able to tell which parallel events have completed and which have not, and when it's ready to move on to the next step. It's something I'll try exploring when I find an excuse to do it during worktime. 😼

genekim 2020-08-02T16:48:25.160700Z

Holy cow! I LOVE the idea of `re-frame-async-flow-fx`!!! Thanks for all your help on this — I’ve had a bit of a breakthrough that has allowed me to build what I want to build… but it’s made it pretty clear that I need to clean my mess up somehow. But I’ve been thinking about all y’all’s advice on how to think about reorganizing — I’ll keep you posted on my plans. (After I finish building this one feature, that’s got me so excited, I woke up early this morning to tackle! 🎉)

💪 1
genekim 2020-08-03T01:06:20.161Z

And here’s the graphviz rendering of my horrible event dispatch graph. :)

uosl 2020-08-03T18:46:27.166600Z

Thanks for sharing the graph! That is very nice, I imagine it would be useful to have for every re-frame project.

➕ 1
genekim 2020-08-04T07:07:15.166900Z

For y’all’s consideration and comment — I wrote up how this graph helped me get something done. If y’all think it’s solid enough, I’d love to post to this channel and @ mention Mike Thompson.

genekim 2020-08-04T07:07:24.167100Z

Many thanks in advance!

genekim 2020-08-04T18:42:44.167800Z

Ooops! Forgot to post the link!!! https://docs.google.com/presentation/d/1yf6f7OYFWYADZ59nrSSNWjiw8zf-yt60yBjWqrJFPdA/edit#slide=id.p

Jose Varela 2020-08-04T18:48:27.168Z

Nice!

Jose Varela 2020-08-04T18:50:32.168200Z

@genekim in this case, what did you end up doing with the output? Did you rearrange some of these relations?

Jose Varela 2020-08-04T18:54:36.168400Z

Got it, “Countermeasure” explains what you ended up doing

genekim 2020-08-04T19:00:37.168900Z

Great catch — new title. :)

genekim 2020-08-04T19:03:02.169500Z

Thx @joservarelaf !

👍 1
Jose Varela 2020-08-04T19:07:59.169800Z

It’d be awesome to show a chart like that and highlight the bubbles as the events are ocurring. Imagine running your app at ‘slow speed’ and seeing the event flow go through the graph.

➕ 1
Jose Varela 2020-08-04T19:08:54.170Z

Or a debugger step-through.. click, click, click and the graph moves

genekim 2020-08-04T19:29:54.170200Z

That blows my mind — that’d be amazing! (Maybe motivation to get a wrapper around MermaidJS… could dynamically render the graph…)

genekim 2020-08-04T19:30:27.170400Z

@joservarelaf Just added screenshot of your idea to the deck. 🙂

Jose Varela 2020-08-04T19:31:14.170600Z

haha thanks! what’s this deck are you giving a talk?

Jose Varela 2020-08-04T19:31:28.170800Z

hmmm maybe this is a great excuse for me to get started with clojure

genekim 2020-08-04T19:32:12.171Z

Oh, just the Google Slides you already saw — no intent to present it. It’s just easier to write in bullet points than actual full prose. 🙂 (PowerPoint/Slides is the tool for the lazy, which isn’t always a bad thing — it’s so efficient. 🙂

Jose Varela 2020-08-04T19:33:54.171200Z

Agree! Been doing something similar too: when there’s something I want to think more about, I schedule a talk at a local meetup (nothing fancy) to force myself to come up with a coherent story hehe

2020-08-05T16:35:46.202200Z

I would look at react-cytoscapejs for rendering networks with a functional interface. I am using it at work and it seems decent.

Jose Varela 2020-08-05T16:37:24.202400Z

Looks awesome! Thanks for the tip

Jose Varela 2020-08-06T03:49:23.215300Z

Check this out, similar to re-frame-10x: https://github.com/flexsurfer/re-frisk Has a feature similar to what we’ve been talking about: https://github.com/flexsurfer/re-frisk#graph-accumulated-for-an-app-life-with-weights-important-with-lots-of-subscriptions-rendering-might-be-slow

uosl 2020-08-08T11:28:55.263200Z

I had no idea re-frisk had accumulated all these features! I thought it was just a app-db explorer.