Fork me on GitHub
#re-frame
<
2020-07-08
>
kimim11:07:53

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

Harshana11:07:57

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-himik11:07:48

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

p-himik11:07:15

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

Harshana11:07:46

My bad! It works just fine.

Harshana11:07:52

How is reagent/with-let different from let ?

p-himik11:07:25

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.

Lu11:07:15

Is with-let equal to reagent form 2?

p-himik12:07:15

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

Lu12:07:48

Thanks 😊

jahson16:07:54

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?

isak16:07:08

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

phronmophobic17:07:30

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?

dpsutton17:07:02

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

dpsutton17:07:16

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

phronmophobic17:07:49

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

dpsutton17:07:01

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

manutter5117:07:08

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.

phronmophobic17:07:51

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

manutter5117:07:10

Ah, ok, that's different 🙂

manutter5117:07:35

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

phronmophobic17:07:11

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

phronmophobic17:07:41

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

phronmophobic17:07:36

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

manutter5117:07:18

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.

manutter5117:07:50

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.

phronmophobic17:07:56

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

manutter5117:07:56

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

manutter5117:07:20

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

manutter5117:07:30

Are you familiar with CQRS?

manutter5117:07:40

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

phronmophobic17:07:33

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 
       ....)))

phronmophobic17:07:25

I guess it just comes down to ergonomics?

manutter5117:07:25

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.

manutter5117:07:53

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.

lilactown17:07:01

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

phronmophobic17:07:24

ah! ok. that seems like an interesting and important difference from views

manutter5117:07:14

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.

phronmophobic18:07:02

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.

manutter5118:07:38

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

phronmophobic18:07:32

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

phronmophobic18:07:55

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

phronmophobic18:07:05

right now, I have to

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

manutter5119:07:30

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.

phronmophobic19:07:44

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

manutter5120:07:37

Ah, without reagent, that explains a lot! 😄

lilactown17:07:12

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

lilactown17:07:36

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

phronmophobic17:07:51

I thought events and effects were enqueued and not run completely ad-hoc?

lilactown17:07:21

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

phronmophobic17:07:49

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

lilactown17:07:52

I should say, multiple dereferences across frames

phronmophobic17:07:40

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

genekim00:07:58

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

phronmophobic00:07:17

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

genekim05:07:56

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! 😉

phronmophobic05:07:50

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.

genekim05:07:14

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!!!

phronmophobic05:07:55

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

genekim05:07:14

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

genekim05:07:35

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

phronmophobic05:07:32

you can capture key events with:

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

genekim05:07:10

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

genekim06:07:11

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])))

phronmophobic06:07:32

on just returns a view that responds to key-presses

phronmophobic06:07:49

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"))))))

genekim06:07:26

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.

genekim06:07:17

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. 🙂

phronmophobic06:07:58

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

phronmophobic06:07:17

i'm just realizing println statements mess with lanterna

genekim06:07:40

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

phronmophobic06:07:16

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

genekim06:07:09

This is exciting — thanks for the help!!

phronmophobic06:07:41

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")))))

phronmophobic06:07:08

this will log to test.log

genekim06:07:25

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

genekim06:07:33

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.).

phronmophobic06:07:02

you can do something similar

genekim06:07:30

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

phronmophobic06:07:45

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

phronmophobic06:07:35

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

phronmophobic06:07:01

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

genekim06:07:13

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!!!

phronmophobic06:07:31

thanks for giving it a shot!

genekim06:07:36

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! 🙂

phronmophobic07:07:15

there area also backends for webgl and the virtual dom

genekim07:07:00

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!

genekim07:07:59

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!!!

phronmophobic07:07:38

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

phronmophobic07:07:50

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

phronmophobic15:07:32

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

genekim15:07:19

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! :)

phronmophobic17:07:18

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