This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-12-13
Channels
- # adventofcode (37)
- # announcements (11)
- # babashka (46)
- # beginners (35)
- # biff (1)
- # clojure (44)
- # clojure-austin (1)
- # clojure-europe (23)
- # clojure-nl (2)
- # clojure-norway (8)
- # clojure-uk (5)
- # conjure (3)
- # cursive (22)
- # data-science (13)
- # docker (11)
- # events (8)
- # hyperfiddle (7)
- # joyride (1)
- # juxt (9)
- # malli (7)
- # matrix (4)
- # pedestal (3)
- # podcasts-discuss (1)
- # portal (1)
- # re-frame (62)
- # reitit (2)
- # releases (1)
- # schema (3)
- # sql (14)
- # squint (3)
- # xtdb (6)
- # yamlscript (4)
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?
(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)
}]
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
.
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 "".
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.
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.
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) ]
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] ...)
.
That's not always the case. Why and how to distinguish the cases is described in the excellent Reagent documentation.
If every cljs developer has to learn from the ground up by learning JS and friends first, cljs loses most of its luster.
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.
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.
But I still don't get why nothing renders. With obj
or without obj
on the inner function, it isn't getting called.
well you would need to pass obj
down into the anonymous function as a param, no?
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.
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.
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
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?
That's the only change:
(defn heedy-string-object [obj]
(let [text (reagent/atom "")]
(fn []
(let [id (:id obj)
objname (:name obj) ]
That's not the only change because you also have the closing parens change. Please just post the whole component.
Hmmm I moved the inner let
bindings to the outer since they never change and removed that inner let. And then they show up.
Alright, I give up. Good luck though, and I'm happy you seem to have gotten it working.
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!
Right. And Reagent docs have a whole section describing ()
vs []
and why the latter should be used most of the time.
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
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.
i also tried putting the subscription directly into the :component-did-mount function, and got the same outcome
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.
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?
your second suggestion seems to be the same as putting my reframe subscription/deref into the lifecycle function
which also hasn't worked for me
> 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?
no unfortunately
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]))})))
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.
you just clone, then ./run.zsh
have to install some stuff with npm as well
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?
just to trigger the re-calling of the lifecycle functions?
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.
ah let me try
you should see a graph rendered on the right of the page at localhost:3000
btw your deref point seems to be working
still messing with it but i see data now
it should have the contents of developments
in it too
(more nodes and edges)
> 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.
this was the key
it seems