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
2020-07-08T11:07:53.302500Z

Hello, any good suggestions on how to authenticate SPA with Azure AD? Thanks.

Harshana 2020-07-08T11:26:57.306300Z

Hey all! I was trying out the following code. When I try to print the atom value outside the button, it doesn't get logged. But inside the onClick fn in the Button, it gets logged. I'm curious why this is happening. Thanks!

(defn view-review-description
  []
  (let [open? (reagent/atom true)]
    (js/console.log "Outside Button" @open?)
    [:div
     [components/Button
      {:onClick
               (fn []
                 (reset! open? true)
                 (js/console.log "Inside button " @open?))

       :style {:margin "0px 5px"
               :color "#00947E"}} "View"]]))

p-himik 2020-07-08T11:38:48.306400Z

It gets print out just fine for me, just as it should.

p-himik 2020-07-08T11:39:15.306600Z

BTW you probably want to use reagent/with-let instead of that let. But it won't affect the printing out in any way.

Harshana 2020-07-08T11:44:46.306800Z

My bad! It works just fine.

Harshana 2020-07-08T11:44:52.307Z

How is reagent/with-let different from let ?

p-himik 2020-07-08T11:53:25.307300Z

With let, you will never be able to set open? to false because it will be reinitialized with (reagent/atom true) on each re-render. reagent/with-let prevents that from happening.

1
Lu 2020-07-08T11:57:15.307900Z

Is with-let equal to reagent form 2?

p-himik 2020-07-08T12:05:15.308100Z

I think so, yes. But don't quote me on that. :) I didn't delve that deep into its implementation.

Lu 2020-07-08T12:10:48.308400Z

Thanks 😊

2020-07-08T16:20:54.310900Z

It happened again. Time after time I see people trying to reuse subscriptions in event handlers, other subscriptions or even functions. I know it's tempting to reuse the code already written without spending more effort. In case of event handlers I can point them to https://github.com/Day8/re-frame/blob/master/docs/FAQs/UseASubscriptionInAnEventHandler.md. In other cases I try to convince people to factor the common part out as a helper / selector / you-name-it, and to try to remain in PureLand, but it's often tedious. I'm not very used to ask questions, but not this time: How do you cope with desire to reuse subscriptions in places other than view components?

isak 2020-07-08T16:39:08.313Z

I cope by remembering the other parts of re-frame, which are well thought-out 🙂

phronmophobic 2020-07-08T17:06:30.316100Z

I'm still new to re-frame, so this is probably a dumb question. In "The Wrong Way" https://github.com/Day8/re-frame/blob/master/docs/FAQs/UseASubscriptionInAnEventHandler.md linked above, it says don't use subscriptions in event handlers > because that subscribe: > might create a memory leak (the subscription might not be "freed") > makes the event handler impure (it grabs a global value) it seems like these drawbacks would also apply to using subscriptions in view components. what's different about view components that make these drawbacks not apply?

dpsutton 2020-07-08T17:09:02.316700Z

components get unmounted and can declare they no longer are interested in the subscription

dpsutton 2020-07-08T17:09:16.317100Z

random functions that are run have no "finished" lifecycle so they never clean up the subscription

phronmophobic 2020-07-08T17:10:49.317700Z

that addresses the memory leak issue. what about the impure/global value issue?

dpsutton 2020-07-08T17:13:01.318Z

are you asking how that makes it impure or why purity is a goal?

2020-07-08T17:15:08.319500Z

consider:

(rf/reg-event-db
 :impure/handler
 (fn [db [_ v]]
   (let [from-db @(rf/subscribe [:app-db/other-value])]
     (assoc db :new-value (+ v from-db)))))

(rf/dispatch [:impure/handler 8])
If :impure/handler were a pure function, you'd be able to tell what value was set for the :new-value key. As it is, there's no way to know, because the return result depends on something other than the fn arguments and its internal logic.

phronmophobic 2020-07-08T17:15:51.320Z

i'm asking why it's ok for view components to be impure

2020-07-08T17:16:10.320300Z

Ah, ok, that's different 🙂

2020-07-08T17:16:35.321100Z

A view function is not impure, it's a transformation of some or all of the app-db.

phronmophobic 2020-07-08T17:17:11.321900Z

why are subscriptions in events impure, but subscriptions in views considered pure?

phronmophobic 2020-07-08T17:17:41.322900Z

I don't understand why the drawbacks of subscriptions apply to events, but not to views

phronmophobic 2020-07-08T17:18:36.324500Z

obviously, it seems to work, but since I'm new to re-frame, I don't know what the differences are

2020-07-08T17:19:18.325200Z

It's kind of semantics -- the subscriptions in a view actually are arguments to the view function, but because a subscription also incorporates the mechanics of forcing a re-render whenever the data changes, it's easier to express them as if they were function calls inside a let.

2020-07-08T17:20:50.326300Z

Your question is very interesting though because it does highlight some kind of conceptual mismatch between the desired functionality of the view and the way we express that functionality in Clojure.

phronmophobic 2020-07-08T17:20:56.326400Z

couldn't [making subscriptions arguments to the function] also be done for events?

2020-07-08T17:21:56.327200Z

Well, could but shouldn't. Event handlers have a different role.

2020-07-08T17:22:20.327700Z

(plus of course the implementation details like leaking memory).

2020-07-08T17:22:30.328Z

Are you familiar with CQRS?

phronmophobic 2020-07-08T17:23:04.328300Z

somewhat

2020-07-08T17:23:40.329300Z

I don't know about anyone else, but re-frame seems to me to be basically a CQRS for UI stuff

phronmophobic 2020-07-08T17:24:33.330200Z

it seems like events and views both have mechanisms for injecting subscriptions. for views, I think you just deref a subscription? for events, the subscriptions are on the "outside" of the function:

(re-frame.core/reg-event-fx         ;; handler must access coeffects, so use -fx
  :event-id 
  (inject-sub  [:query-id :param])  ;; <-- interceptor will inject subscription value into coeffects
  (fn [coeffects event]
    (let [sub-val  (:something coeffects)]  ;; obtain subscription value 
       ....)))

phronmophobic 2020-07-08T17:25:25.330900Z

I guess it just comes down to ergonomics?

2020-07-08T17:26:25.332200Z

Yeah, that's the canonical way to do it. Event handlers receive coeffects and events, so if you add an effect handler that injects the value of a subscription, you're keeping it theoretically pure--the effect handler isn't accessing any globals that way, it's just operating on the date it receives in its arguments.

2020-07-08T17:28:53.334200Z

I think some of the things you're questioning though are hints that there may be other ways of approaching the whole conceptual framework of subscriptions and effects and event handlers and views, so it's definitely probing into the matter further. But I think if you find good answers to your questions you'll be designing something that builds on re-frame's example in order to create something new and different.

lilactown 2020-07-08T17:31:01.336300Z

the “problem” with using subscriptions inside of events or effects is that events and effects by design do not participate in the reactive graph

2020-07-08T17:31:14.336900Z

Subscriptions combine ("complect") the problems of "How do I get efficient access to the parts of app-db that I want to render?" and "How do I make sure I re-render components when their underlying data changes?" Maybe there'd be a different way of solving these problems separately that would reduce or eliminate the "subscriptions inside events" prohibition.

✔️ 1
lilactown 2020-07-08T17:32:12.337900Z

so you can end up with “tearing”, where you dereference a subscription in a view, dispatch an event, that event dereferences the subscription and the value has changed between dispatch and running the event

lilactown 2020-07-08T17:33:36.339700Z

the contract between subscription and component is that when a subscription changes, a component will be re-rendered accordingly. there’s no contract like that with events or effects, and because they can be fired ad-hoc asynchronously you cannot depend on multiple dereferences being coherent

☝️ 1
lilactown 2020-07-08T17:34:52.341200Z

I should say, multiple dereferences across frames

phronmophobic 2020-07-08T17:36:40.342600Z

my side project has been a clj/cljs based graphics and event library (an alternative to the DOM). based on re-frames success in the browser, my goal is to try to use re-frame as the ui state library. hopefully, I can learn enough about re-frame to make it work well. here's the proof of concept so far, https://github.com/phronmophobic/membrane-re-frame-example

genekim 2020-07-13T00:17:58.401700Z

OMG. I love it! re-frame to build terminal apps! so great!!!

phronmophobic 2020-07-13T00:20:17.401900Z

😄 awesome. let me know if you have any questions, issues, or feedback!

genekim 2020-07-13T05:19:56.402200Z

Holy cow, I just checked out your examples — so freaking cool! To set some context: I work on a bunch of programs where I’m the only user, and they mostly are SPAs that run in Heroku. But, holy cow, the effort to learn about the DOM and JavaScript is often so much more than I what I thought I was signing up for. Then throw in CORS and other madness on the backend, and the fun factor plummets even more. I was casually looking at cljfx, to see if that would be any easier, and holy smokes, that just feels too alien — I haven’t learned either Swing or JavaFX yet… This seems more my speed — I just wrote my first lanterna program, and I love the idea that I could have my re-frame programs target the terminal! It almost makes me want to weep with happiness!!! I’m going to write a toy app in the next day or two! Maybe even tonight! Keep up the amazing work!!! Now I’m dying of curiosity: what is your motivation to write this library, which seems like such a throwback to one that died decades ago! 😉

phronmophobic 2020-07-13T05:29:50.402400Z

It's still a work in progress, so don't hesitate to ping me if you run into any issues. The back story is pretty long winded, but the short version is that I was trying to build an app and the available options for building UIs for a jvm based clj app all seemed full of incidental complexity.

genekim 2020-07-13T05:35:14.402600Z

Okay, I just wrote my first lanterna program that handled a couple of keystrokes, and already, I see why you might want something at a much higher level, like re-frame. Starting my first attempt at doing this in membrane! Woot! (I love where you’re going with it! Yes, so much complexity that I just don’t have any appetite to take on. I bought a book on Swing, but yowza, it’s hundreds of pages long! But, there are days I’d rather learn that than deal with client/server, etc…) Okay, I’ll hopefully have something to have a status update in 30m or so! 🙂 So fun!!!

phronmophobic 2020-07-13T05:35:44.402800Z

😄

phronmophobic 2020-07-13T05:38:55.403Z

i'm actually pretty new to re-frame, so I'm sure the integration there can be improved. specifically, I'd like to have a text input component that has its state idiomatically handled by re-frame, but I don't know enough about re-frame to know what that should look like. my first attempt was just to try and include a text input that worked similarly to the text input available in the browser

genekim 2020-07-13T05:44:14.403200Z

Okay, I’ve got the app running in the REPL — what’s the easiest way to capture a keydown event, when there’s no focus in a textbox, etc? (Gosh, I don’t even know the language for this — a global keypress event?) PS: to my knowledge, there isn’t a textbox element like that. I think things like that are in re-com, which is a set of general purpose UI elements, inspired by Adobe Flash, etc.: https://github.com/day8/re-com

genekim 2020-07-13T05:45:35.403600Z

(^^^ hmm, no form elements in re-com. I think I already led you astray!)

phronmophobic 2020-07-13T05:46:32.403800Z

you can capture key events with:

(on :key-press
    (fn [s])
    (ui/label "hello"))

genekim 2020-07-13T05:47:10.404Z

Cool! Thank you! (I’ll be right back. 🙂

genekim 2020-07-13T06:10:11.404200Z

Can I put the (on :key-press) in the -main function? I’m trying to convert a keypress event into a re-frame event…

(defn -main [& args]
  (println "main starting")
  (dispatch [:initialize-db])
  (println "hello")
  (on :key-press
      (fn [s]
        (println "main: keypress event: " s)
        (ui/label "Hello from key")
        (dispatch [:keydown s])))

phronmophobic 2020-07-13T06:11:32.404400Z

on just returns a view that responds to key-presses

phronmophobic 2020-07-13T06:13:49.404600Z

you'll probably want something like:

(defn -main [& args]

  (dispatch [:initialize-db])

  (lanterna/run #(memframe/re-frame-app
                  (fn []
                    (on :key-press
                        (fn [s]
                          (println "main: keypress event: " s)
                          [[:keydown s]])
                        (ui/label "Hello from key"))))))

genekim 2020-07-13T06:14:26.404800Z

Ah, I understand your examples better now. What is easiest way to capture keypress events when there is no focus anywhere? In my head, I was thinking about making a vi like editor, accumulating text, capturing arrow keys to move cursor around. Without using any widgets. Sorry for my denseness.

genekim 2020-07-13T06:15:17.405Z

PS: regarding your question: I’d ask Mike Thompson directly in the channel — seriously, this is the friendly channel in Clojurians, which is already super friendly. 🙂

phronmophobic 2020-07-13T06:15:58.405200Z

the :key-press event should capture capture key-presses regardless of focus

phronmophobic 2020-07-13T06:18:17.405500Z

i'm just realizing println statements mess with lanterna

genekim 2020-07-13T06:19:40.405700Z

Oh! Sorry, I missed your code example while I was typing — trying it now. (How does one log debug stuff, if not in println?)

phronmophobic 2020-07-13T06:22:16.405900Z

I think the example I gave you isn't quite right. fixing...

genekim 2020-07-13T06:25:09.406100Z

This is exciting — thanks for the help!!

phronmophobic 2020-07-13T06:30:41.406300Z

ok, here's an example that should actually work:

(defn -main [& args]
  (dispatch [:initialize-db])  
  (lanterna/run #(memframe/re-frame-app
                  (on
                   :key-press
                   (fn [s]
                     (spit "test.log" (str "keypress: " s "\n") :append true)
                     [[:keydown s]]
                     )
                   (lanterna/label "hello world")))))

phronmophobic 2020-07-13T06:31:08.406500Z

this will log to test.log

genekim 2020-07-13T06:31:25.406700Z

Ha! I see the logging mechanism. 🙂 Trying again…

genekim 2020-07-13T06:40:33.406900Z

Got it! And it’s working in the terminal version, inside of term-view namespace. That’s super cool. I’m assuming I can do the exact same thing in views namespace, that uses a Swing window? (This is what I tried first.).

phronmophobic 2020-07-13T06:42:02.407100Z

you can do something similar

genekim 2020-07-13T06:42:30.407300Z

(Oops. Didn’t mean to hit Enter and send that. Hacking away on it right now. 🙂

phronmophobic 2020-07-13T06:42:45.407500Z

you'll want to use (membrane.ui/label "hello world") instead of (lanterna/label "hello world")

phronmophobic 2020-07-13T06:43:35.407700Z

since font-height doesn't really make sense in a terminal view, but it does make sense in a desktop view

phronmophobic 2020-07-13T06:46:01.407900Z

so, for the desktop view:

(defn -main [& args]
  (dispatch [:initialize-db])  
  (skia/run #(memframe/re-frame-app
              (on
               :key-press
               (fn [s]
                 (spit "test.log" (str "keypress: " s "\n") :append true)
                 [[:keydown s]]
                 )
               (ui/label "hello world")))))
the differences being: 1. ui/label instead of lanterna/label 2. skia/run instead of lanterna/run

genekim 2020-07-13T06:47:13.408200Z

Got it!!! And println works when in a Swing window! 🙂 Gimme 10m, and I think I’ll have my first stateful program running! 🙂 Thank you!!!

🦜 1
phronmophobic 2020-07-13T06:47:31.408500Z

thanks for giving it a shot!

genekim 2020-07-13T06:59:36.408700Z

Well, wow, that is super cool! Thank you so much for writing this! I will be thinking for days about how I can use this — brilliant work!!! Here’s my caveman output that I’m so delighted by! 🙂

🎉 1
1
phronmophobic 2020-07-13T07:02:15.409300Z

there area also backends for webgl and the virtual dom

genekim 2020-07-13T07:08:00.413400Z

Whoa. I don't even know what those mean, but I can't wait to research it! Things I'm now dying to take a stab at: a simple vi modal editor, a snake game, a Trello card browser (based on an existing re-frame app)... I'd love to submit them as sample apps, because I'm so excited by what you've built. It seems like the easiest way to write simple programs. THANK YOU!

👍 1
genekim 2020-07-13T07:11:59.416600Z

I'm even imagining taking an existing re-frame SPA apps (CLJS client, CLJ backend), and adding one more target for a membrane CLJ app (without the ring server, CLJS client)... (but it might be tough to live without all the awesome hot code reloading — you sure can get spoiled by that! :) Have a great night!!!

phronmophobic 2020-07-13T07:14:38.416900Z

hot code reloading works really well for the desktop backend. I haven't thought too much about how hot code reloading should work for terminal apps, but it's definitely possible

phronmophobic 2020-07-13T07:25:50.417100Z

looks like github is down, but you can find more docs at https://github.com/phronmophobic/membrane/

phronmophobic 2020-07-13T15:28:32.417500Z

there's also a #membrane channel if you have more questions or feedback

genekim 2020-07-13T15:30:19.418700Z

Good morning — thanks for the help last night! I had trouble going to sleep last night, as I was excitedly thinking about things I could build! :)

😄 1
phronmophobic 2020-07-08T17:37:18.343600Z

so far, re-frame seems like a pretty good fit. I can reuse all the subs, events, and db for the terminal app and for the desktop app. the view code is different for each

lilactown 2020-07-08T17:40:21.344100Z

they are queued but you can’t control when a user clicks a button

phronmophobic 2020-07-08T17:40:49.344300Z

right, but the event handler doesn't run when the user clicks the button. it runs when re-frame decides to dispatch the event

phronmophobic 2020-07-08T17:41:09.344500Z

right?

lilactown 2020-07-08T17:45:50.344700Z

yes

phronmophobic 2020-07-08T18:11:02.345300Z

right now, I'm having the opposite problem. I would like to call view functions without having to put data into the global app db.

2020-07-08T18:21:38.345500Z

You mean you have data that you want to keep separate from app-db?

phronmophobic 2020-07-08T18:23:32.345700Z

I just want to be able to call a view function and pass in the data rather than put some data in the the app db and then call the view function to see what the view function produces

phronmophobic 2020-07-08T18:23:55.345900Z

(todo-item {:description "write code" :completed false})

phronmophobic 2020-07-08T18:25:05.346100Z

right now, I have to

(dispatch [:add-todo {:description "write code" :completed false}])
;; then
(todo-item)

phronmophobic 2020-07-08T18:26:04.346400Z

i'm probably doing something wrong, but I was following https://github.com/day8/re-frame/tree/master/examples/todomvc see: https://github.com/day8/re-frame/blob/master/examples/todomvc/src/todomvc/views.cljs#L27

2020-07-08T19:09:30.346800Z

You can certainly make a view component with arguments that you pass in and render. I commonly do something like that when rendering tables with rows of data: the parent component gets a list of rows and then creates child components by code very much like your todo-item (except I use square brackets not parens). The thing is, if you write a component that works this way, then (a) where are you getting your data from? and (b) how do you control the rendering/re-rendering? In the case of a parent component rendering a list of rows, then it's straightforward: it gets its data from the parent (which gets it from a subscription), and it re-renders whenever the parent component re-renders.

phronmophobic 2020-07-08T19:30:44.347Z

i realize I'm off in the weeds since I'm trying to use re-frame without reagent. I guess the question you're asking is the same question I'm trying to solve. thanks for taking the time and I totally understand if it's not worth going down this rabbit hole since this isn't how re-frame is intended to be used. currently, my proof of concept doesn't support the square bracket syntax used by reagent. see the todo-item call below:

(defn task-list
  []
  (let [visible-todos @(subscribe [:visible-todos])
        all-complete? @(subscribe [:all-complete?])]
    (apply
     vertical-layout
     (interpose
      (ui/spacer 0 10)
      (for [todo visible-todos]
        (todo-item todo))))))
I would like to be able to call task-list and pass in data for testing and dev tools, but I'm not sure what the best way forward is there. I could reimplement some of the reagent functionality, but I feel like that pattern is unnecessary in a context without the DOM

2020-07-08T20:45:37.347500Z

Ah, without reagent, that explains a lot! 😄

1