Fork me on GitHub
#re-frame
<
2023-12-13
>
Nundrum16:12:50

Here's a behavior question. I have a component with a text field. With :on-key-down for 13 it dispatches an event, which works. The text remains in the field. If click the field and hit enter again, the event dispatches but the value is blank. Alternatively if I try to reset! the ratom to blank it doesn't work. The text remains in the field. A println before and after shows the correct result in the console log. What am I missing?

p-himik16:12:16

Can't answer without any code.

Nundrum16:12:34

(defn heedy-string-object [obj]                                                       
 (let [text   (reagent/atom "")                                                     
       schema (get-in obj [:meta :schema])    ■ warning: unused binding schema     
       id     (:id obj)                                                             
       objname (:name obj)                                                           
       ]                                                                             
   ^{:key id} [:div.heedy-object                                                     
    [:p.heedy-object-description (or (:name obj) (:description obj))]                
    [:div.heedy-entry-line                                                           
      [:input.heedy-string {:size :14                                                
                            :type :text                                              
                            :name objname                                            
                            :on-change #(reset! text (-> % .-target .-value))        
                            :on-key-down                                             
                              #(case (.-which %)                                     
                                 13 (do                                              
                                      (println "before" @text)                       
                                      (re-frame/dispatch                             
                                        [::events/add-to-basket [id objname @text]])  
                                      (reset! text "")                               
                                      (println "after" @text)                        
                                      )                                              
                                 nil)                                                
                            }]

p-himik16:12:39

Your ratom is overwritten on every render, making it useless. You have to use a form-2 or a form-3 component, or reagent/with-let.

Nundrum16:12:16

If it's getting overwritten, then how does it work at all with the dispatch? How are the two printlns showing the results I expect - first the value of @text and then "".

p-himik16:12:39

Ah, you don't dereference the ratom during the render. In any case, ratoms are not supposed to be used this way, so I wouldn't spend any amount of time on understanding what exactly is going on. It's better to introduce a proper fix and move on.

p-himik16:12:33

If I were you, I'd make the input a controlled component, put the whole text into re-frame's app-db, move the input inside a form, and dispatch the add-to-basket event on a submission of that form while preventing the default action on the submit event.

Nundrum17:12:53

I updated it to return a function, but then all my divs disappeared

(defn heedy-string-object [obj]    
 (let [text   (reagent/atom "")]  
   (fn []                         
     (let [id     (:id obj)      
           objname (:name obj) ]

p-himik17:12:51

That's why some time ago, when you were asking about a way to learn about frontend development, I have suggested learning from the ground up and not in reverse. :) Can't say what's going on without seeing the full state of the current code that you have, but that inner (fn [] ...) should very likely be (fn [obj] ...).

p-himik17:12:19

That's not always the case. Why and how to distinguish the cases is described in the excellent Reagent documentation.

Nundrum17:12:06

If every cljs developer has to learn from the ground up by learning JS and friends first, cljs loses most of its luster.

p-himik18:12:17

Not really. I never said that you have to learn the whole JS. Learning just the basics and the most frequently used API is enough and will go a long way while taking just a couple of days. Same deal with React and Reagent. You don't have to know everything in them by heart. But reading the most salient parts of the docs is IMO a must because it's the building blocks on which everything rests.

Nundrum18:12:27

I have read through the Reagent docs

p-himik18:12:31

Somewhat related - I remember teaching a junior Python programmer some pointer arithmetic from C and how memory works in general. It might surprise you but it ended up being quite useful for her because it let her have a much better grasp on the all the indexing patterns of Pandas dataframes. And it took me like half an hour to explain all that in a way that she understood.

Nundrum18:12:25

But I still don't get why nothing renders. With obj or without obj on the inner function, it isn't getting called.

p-himik18:12:44

Oh, so nothing renders? Or just that component?

Ben Lieberman18:12:25

well you would need to pass obj down into the anonymous function as a param, no?

p-himik18:12:05

Most likely, yes. But Numdrum says that that didn't change anything.

1
Nundrum18:12:26

Just that component. It behaves like the render function is never called.

Nundrum18:12:51

obj isn't being updated - I just need the :name and :id from it and those always stay the same. So I think it's fine for it to be closed over? Only the ratom needs to be updated for the text field.

p-himik18:12:36

If you turn a form-1 component into a form-2 component, it should render for the first time regardless of how you pass things around. If it's not rendered after the change, you have done something wrong.

Nundrum18:12:47

Yeah, obviously so 😉 Any guesses as to what, though? Here's the full code. Where it was before I updated to form-2: https://github.com/robbieh/heedyfeedy/blob/dev/src/heedyfeedy/views.cljs#L35

p-himik18:12:35

Well, if it's the change to form-2 that's wrong (given how that exact change made it stop render), how can I help if I don't have that new code?

Nundrum18:12:47

That's the only change:

(defn heedy-string-object [obj]    
 (let [text   (reagent/atom "")]  
   (fn []                         
     (let [id     (:id obj)      
           objname (:name obj) ]

p-himik18:12:25

That's not the only change because you also have the closing parens change. Please just post the whole component.

Nundrum18:12:56

Hmmm I moved the inner let bindings to the outer since they never change and removed that inner let. And then they show up.

p-himik18:12:17

Alright, I give up. Good luck though, and I'm happy you seem to have gotten it working.

Nundrum19:12:00

Thanks! I'm glad it works. Long story short, it broke after reloading the page and worked again after changing (heedy-string-object) to [heedy-string-object] And now I'm glad I understand why!

p-himik19:12:46

Right. And Reagent docs have a whole section describing () vs [] and why the latter should be used most of the time.

Kovas Palunas21:12:13

Is there some documentation i'm missing about how to use form-3 reagent components with re-frame subscriptions? In my code at https://github.com/kovasap/cljs-board-game/blob/59ab8948727fa676c39bdf25a66d80956184962d/src/main/app/interface/view/developments.cljs#L123 I'm trying to get some data from a subscription (`developments`) into a :component-did-mount react lifecycle function. However, when I run the code, my data always resolves to nil. I've tried at multiple layers in the code with the same result, and am getting the sense that i'm misunderstanding something fundamental here

p-himik21:12:13

The developments argument is not a subscription though. Its value does come from a subscription from a wrapping component, but it by itself is not a subscription. When you pass developments to the cytoscape-resource-flow component, the value gets closed over. The :component-did-mount will be called with that value, but nothing else will happen if the value changes on the outside. I don't know how you can get nil there though - sort-by always returns a collection, even if an empty one.

Kovas Palunas22:12:20

i also tried putting the subscription directly into the :component-did-mount function, and got the same outcome

p-himik22:12:45

Two ways around it. The first one - make the component non-reactive, accepting all the data from the outside. Its :component-did-mount function should do what it does, and you also must implement the :component-did-update function so it refreshes whatever the previous function has set up. The second one - similar, but deref all reactive things in the render function (no need to use the deref'ed values, you just gotta deref the reactions/ratoms so that Reagent registers them). And do the same thing with the lifecycle functions.

Kovas Palunas22:12:02

hmm if i just copy the :component-did-mount into a :component-did-update key, i get the same outcome. i don't think this is exactly what you were recommending, but should do the same thing?

Kovas Palunas22:12:00

your second suggestion seems to be the same as putting my reframe subscription/deref into the lifecycle function

Kovas Palunas22:12:04

which also hasn't worked for me

p-himik22:12:29

> i don't think this is exactly what you were recommending, but should do the same thing? Depends on the JS library that you're using, no idea. > your second suggestion seems to be the same as putting my reframe subscription/deref into the lifecycle function It's not the same. Are there any errors/warnings in the JS console in the browser?

Kovas Palunas22:12:53

no unfortunately

Kovas Palunas22:12:31

how does your second suggestion differ from

:component-did-mount (fn [_]
                              (cytoscape
                                (clj->js
                                  {:style     [{:selector "node"
                                                :style    {:background-color
                                                           "#666"
                                                           :label
                                                           "data(label)"}}
                                               {:selector "edge"
                                                :style    {:width 2
                                                           :line-color "#ccc"
                                                           :target-arrow-color
                                                           "#ccc"
                                                           :curve-style
                                                           "bezier"
                                                           :target-arrow-shape
                                                           "triangle"
                                                           :label
                                                           "data(label)"}}]
                                   :layout    {:name "circle"}
                                   :userZoomingEnabled false
                                   :userPanningEnabled false
                                   :boxSelectionEnabled false
                                   :container (js/document.getElementById
                                                graph-element-id)
                                   :elements  (make-development-graph
                                                @(rf/subscribe [:blueprints]))})))

p-himik22:12:31

Using a reactive thing inside only a lifecycle method will not re-run that method on the reactive thing value change. I'm also not certain about whether lifecycle methods are considered a reactive context or not. If not, you'll get a warning from re-frame.

p-himik22:12:42

Since you have a public repo, how do I reproduce this?

Kovas Palunas22:12:59

you just clone, then ./run.zsh

Kovas Palunas22:12:57

have to install some stuff with npm as well

Kovas Palunas22:12:33

ah do you mean that i may need to subscribe in the render method as well, even if i don't ever use the data there?

Kovas Palunas22:12:01

just to trigger the re-calling of the lifecycle functions?

p-himik22:12:36

The subscribe call can be on the inside of the wrapper function and outside of create-class. You just gotta deref the resulting reaction in the render function, yeah.

p-himik22:12:33

I got the app running. But how do I actually reproduce the behavior?

Kovas Palunas22:12:57

you should see a graph rendered on the right of the page at localhost:3000

Kovas Palunas22:12:05

btw your deref point seems to be working

Kovas Palunas22:12:18

still messing with it but i see data now

Kovas Palunas22:12:33

it should have the contents of developments in it too

Kovas Palunas22:12:43

(more nodes and edges)

p-himik22:12:22

> btw your deref point seems to be working Ah, alright. So I assume you got it from here. :) The first approach should've worked just as well. Must've been something wrong in the did-update method or something.

Kovas Palunas22:12:24

yep all working. thanks!

👍 1
Kovas Palunas22:12:04

this was the key