Fork me on GitHub
#reagent
<
2023-01-17
>
jcb17:01:36

Apologies for the terrible example below. I'm attempting to have an element to know where it is on screen, and if it is in a certain position change either it's rendered children or styling. However, I can't figure out how to do this since this info is only available after it has loaded. the below code fails, as it can't access the info until after it is rendered obviously. Is there a way to defer a function call until after the component is loaded? Or if the ref is populated. I realise that this will probably lead to a weird glitch in the rendering but I can't think of another way to handle this issue.

(defn title-line []
      (let [!ref (atom nil)
            pos (atom nil)
            ]
           [re/v-box
            :attr {:ref  (do (fn [el] (reset! !ref el))
                             (swap! pos (.-y (.getBoundingClientRect @!ref)))
                             )
                   :on-click #(js/console.log (.-y (.getBoundingClientRect @!ref)))
                   }
            :children [
                       [:div
                        {:style {:color "red"
                                 }}
                        (str @pos)]
                       
                       ]]

           )
      )

p-himik17:01:26

What do you mean by "fails"? The ref should be set only after the component is rendered.

jcb18:01:01

Yes, but the swap of values into "pos" doesn't work and I'm not sure when I could call that function after the component has rendered. The on-click works as expected, but I would like to do this without user interaction. this probably means that I'm barking up the wrong tree.

p-himik18:01:08

When you write stuff like "fails" or "doesn't work" you really should specify what your expected behavior is and what exactly the observed behavior is. "Fails" is not a behavior. But after a closer look at your code, it's clear that you used swap! wrong. The function accepts another function and not the value itself. You should use reset! instead.

jcb18:01:27

I'm not sure, because the error is that it is returning null, as the property can't be accessed until it is rendered

p-himik18:01:07

The :ref function is called after the component is rendered.

jcb18:01:57

I'm not getting that behaviour, (reset! pos (.-y (.getBoundingClientRect @!ref))) also throws an error at that point

p-himik18:01:43

As I mentioned above - what error?

p-himik18:01:57

Oh, wait, another thing - you are not passing a function to :ref. Why do you have that do block there?

p-himik18:01:30

Both calls to reset! should be inside that (fn [el] ...) and there should be no do altogether.

jcb18:01:58

error is Uncaught TypeError: Cannot read properties of null (reading 'getBoundingClientRect') with or without the do block.

p-himik18:01:42

Yeah, which should go away when you fix the do mistake.

p-himik18:01:53

What is your current code?

jcb18:01:57

(defn title-line []
      (let [!ref (atom nil)
            pos (atom nil)
            ]
           [re/v-box
            :attr {:ref  (fn [el] (reset! !ref el)
                             (reset! pos #(.-y (.getBoundingClientRect @!ref)))
                                 )
                   :on-click #(js/console.log (.-y (.getBoundingClientRect @!ref))) ;works
                   }
            :children [
                       [:div
                        {:style {:color (if (< @pos 130) "red" "blue")
                                 }}
                        (str @pos "position")]

                       ]]

           )
      )

p-himik19:01:34

Why do you have # in there now? It should be just (reset! pos (.-y ...)).

p-himik19:01:30

Some meta-comments: • Always format your code properly. It helps you see the overall structure much better - it would've helped avoid the do mistake. Your IDE should help you with that, e.g. in Cursive I just press Ctrl+Shift+L after any reasonable amount of changes (new lines I still have to arrange myself) • Whenever you're stuck, split the problem into blocks and think about each individual block separately. Learn the parts of every block so you can always model what will happen in your head - that would've helped avoid other mistakes with swap! and # and thinking that :ref is to blame

jcb19:01:47

without the # it throws the same error. I appreciate the advice, I'm very much a constant beginner

p-himik22:01:24

What exactly is your current code? What exactly is the error, including the stacktrace?

jcb22:01:00

hi, I got rid of the stacktrace, however this still isn't actually working as expected, the comparison in the style of the div doesn't have any effect, all text is red no matter the values in the atom pos. The inner and the outer logs both return the same values, but this makes sense as they've already been rendered at that point and aren't moving.

(defn title-line []
      (let [!ref (atom nil)
            pos (atom nil)
            ]
           [re/v-box
            :attr {:ref (fn [el] (when el
                                       (reset! !ref el)
                                       (reset! pos (.-y (.getBoundingClientRect @!ref)))
                                       )
                                 )
                   :on-click #(js/console.log (str "outer" @pos))
                   }
            :children [
                       [:div
                          {:style {:color (if (> @pos 300.00)
                                            "white"
                                            "red"
                                            ) }
                           :on-click #(js/console.log (str "inner" @pos))
                           }
                          "TITLE"]
                       ]
            ]

           )
      )

p-himik22:01:36

That's because you used a plain let at the very root of the component - the ratoms are reset on each re-render. Instead, you can use one of: • A form-2 component • A form-3 component (you'd be able to use :component-did-mount instead of :ref then, which is IMO a bit more clean) • reagent.core/with-let instead of that let All are plentifully documented in the Reagent docs, which I suggest reading in their entirety as they'll make writing future code much easier.

jcb22:01:01

thank you @U2FRKM4TW! With-let solved it immediately, but I'll look up the form-3 component tomorrow.

👍 2
hifumi12320:01:25

How do people here handle component-local state in forms? I have a component as follows:

(defn form []
  [:form
    [part-one]
    [part-two]
    [:button {:type :submit} "Submit"]
    [:button {:type :reset} "Reset"]])
and in e.g. part-one I have a form-2 component like so
(defn part-one []
  (let [data (r/atom nil)]
    (fn [] #_...)))
Do I really have to hold state in form then pass it as a prop to each child component? For additional context: I'm using re-frame but I hold local ratoms for on-change events and the value of input components. The app-db gets updated when an on-blur event is fired.

p-himik22:01:52

> Do I really have to hold state in form then pass it as a prop to each child component? Yes. Pretty much in the same exact way you'd do it in plain React. You can potentially use context but that would be much more work for no real gain in this case.

hifumi12322:01:40

I know I could potentially make on-change dispatch re-frame events instead. But I'm worried about the performance "penalty" of that. For now I'll just pass around a ratom to the children props. Thanks for the feedback!

dvingo01:01:59

I would recommend looking into https://react-hook-form.com/ you can use it with reagent function components more info: https://cljdoc.org/d/reagent/reagent/1.1.0/doc/tutorials/react-features

hifumi12302:01:20

is it possible to integrate react-hook-form with clojure spec? im not sure how easy it'd be to integrate my input components into this library

hifumi12303:01:54

I found a promising library called https://github.com/luciodale/fork which I may experiment with later. Looks to have solved the problems I've been getting with re-frame, form state, form validation

dvingo12:01:34

yea, pretty straightforward to integrate with spec - you can copy what I did for use with malli: https://github.com/dvingo/malli-react-hook-form

2
2
Clojuri0an21:01:13

how to use with nextjs

Clojuri0an21:01:23

what are other clojure frameworks that just work

Clojuri0an21:01:34

but that don't need react, I want to use mithriljs