reagent

Ivana 2024-01-06T07:51:02.531729Z

Hi! My code below works, but can it be implemented less idiotic way? πŸ™‚

(defn- user-answer [right-answer on-success on-fail]
  (let [local-state (r/atom nil)
        check-fn (fn [] (when-let [answer (some-> @local-state :user-input str str/trim not-empty)]
                          (let [real-right-answer (:right-answer @local-state)] ;; can't rely on right-answer here!
                            (prn answer real-right-answer)
                            (if (= answer real-right-answer)
                              (on-success)
                              (on-fail))
                            (swap! local-state dissoc :user-input))))]
    (r/create-class
     {:component-did-mount
      (fn [this]
        (let [initial-right-answer (second (r/argv this))]
          (prn :mount initial-right-answer)
          (swap! local-state assoc :right-answer initial-right-answer)) ;; catch right-answer into local-state
        (when-let [input (.getElementById js/document "user-input")]
          (.addEventListener input "keyup" (fn [e] (when (= 13 (.-keyCode e)) (check-fn))))))

      :component-did-update (fn [this old-argv old-state snapshot]
                              (let [updated-right-answer (second (r/argv this))]
                                (prn :update updated-right-answer)
                                (swap! local-state assoc :right-answer updated-right-answer))) ;; update right-answer in local-state
      :reagent-render
      (fn [right-answer on-success on-fail]
        [:div
         [:span (str "hint-from-render: " right-answer)] " "
         [:span (str "hint-from-ratom: " (:right-answer @local-state))] " "
         [:input {:type "text"
                  :id "user-input"
                  :value (:user-input @local-state)
                  :on-change #(swap! local-state assoc :user-input (.. % -target -value))
                  :auto-complete :off}]
         [:button {:on-click check-fn
                   :disabled (str/blank? (:user-input @local-state))}
          "Check answer"]])})))

p-himik 2024-01-06T08:54:08.789109Z

Don't use getElementById, use React refs instead. No need to set the initial value of local-state in :component-did-mount - you can do it right in the top let since the values of props won't change between those two points. No need to use .addEventListener explicitly - you can add a :on-key-up property to :input. Maybe I'm missing something - why did you split :on-change and "keyup"? Why not handle everything in :on-change?

Ivana 2024-01-06T08:57:13.870249Z

My thoughts was that: I need to force check on input press enter key => I need addEventListener => I need element => I need 3-rd form and :componentDidMount. I'l try your suggestions and share the results

p-himik 2024-01-06T09:06:33.346939Z

Ah, didn't notice the check for code 13. I'd replace the top div with a form and do everything in its :on-submit, along with preventing the default action of the event.

p-himik 2024-01-06T09:07:31.445379Z

In the end, you'll probably end up with a form-2 component. I don't see how a form-3 one would be needed here.

Ivana 2024-01-06T09:09:04.956369Z

If I use addEventListener I need element for finding by id and guarantee that it is already in DOM, so I need :component-did-mount event and add listener there

Ivana 2024-01-06T09:11:13.896639Z

I thought about form & submit but have no experience of using forms with actions other that fetch or open dialogs. Meybe I can use on-submit & preventDefault

Ivana 2024-01-06T09:17:19.480909Z

But before form implementation - thanks, :on-key-up works! And I do not need event listeners and 3-rd form 😁

Ivana 2024-01-06T09:20:59.203779Z

This works, and it looks much better that initial impl

(defn- user-answer [right-answer on-success on-fail]
  (r/with-let [answer (r/atom nil)]
    (let [check-fn (fn [] (when-let [ans (some-> @answer str not-empty)]
                            (prn ans right-answer)
                            (if (= ans right-answer)
                              (on-success)
                              (on-fail))
                            (reset! answer nil)))]
      [:div
       [:span (str "hint " right-answer " ")]
       [:input {:type "text"
                :value @answer
                :on-change #(reset! answer (.. % -target -value))
                :on-key-up (fn [e] (when (= 13 (.-keyCode e)) (check-fn)))}]
       [:button {:on-click check-fn
                 :disabled (str/blank? @answer)}
        "ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΈΡ‚ΡŒ ΠΎΡ‚Π²Π΅Ρ‚"]])))

p-himik 2024-01-06T09:23:41.183629Z

> If I use addEventListener I need element for finding by id and guarantee that it is already in DOM Yes. > so I need :component-did-mount event and add listener there Not necessarily, refs can help here. > Meybe I can use on-submit & preventDefault Yes. Would also remove the need to check for 13 and call check-fn in two places (forms are submitted automatically when the <button> there is pressed or when Enter is hit with the focus being in one of the form's inputs).

πŸ”₯ 1
Ellis 2024-01-06T09:25:57.630279Z

https://github.com/reagent-project/reagent/blob/master/doc/FAQ/UsingRefs.md reagent docs on refs, they do indeed work much better here than getElementById On first render the atom is nil, but once it renders the atom is swapped and then always has the ref. A simple when @ref check is all that's needed to ensure existence

πŸ”₯ 1
Ivana 2024-01-06T09:25:58.658519Z

Thanks alot, I already have much better version cause of :on-key-up and will go further with form & on-submit

Ivana 2024-01-06T10:55:16.200309Z

Even form-1 without refs (still don't understand them properly) and with some googled form- and style- magic 😊

(defn- user-answer [right-answer on-success on-fail]
  [:form {:on-submit (fn [e]
                       (.preventDefault e)
                       (when-let [answer (some-> e .-target js/FormData. (.get "input") str not-empty)]
                         (if (= answer right-answer)
                           (on-success)
                           (on-fail))
                         (-> e .-target .reset)))}
   [:span (str "hint " right-answer " ")]
   ;; 
   ;; 
   [:style "input:invalid + button { opacity: 0.3; pointer-events: none; }"]
   [:input {:name "input"
            :required true
            :auto-complete :off}]
   [:button "Check answer"]])

p-himik 2024-01-06T11:07:19.075679Z

Nice. A few other things: β€’ No need to call str in :span - you can use [:span "hint " right answer " "] β€’ :style is technically not valid outside of :head and some other metadata-accepting elements, but browsers still accept it β€’ That :style affects the whole page, not just the user-answer component

Ivana 2024-01-06T11:09:05.293869Z

Yep, but still can't get worked inline map-based hiccup :style {...} with pseudo-classes

p-himik 2024-01-06T11:12:48.783749Z

A proper CSS/SCSS/whatever file would be best. :) Alternatively, you can use a form-2 component and make inline styles conditional.

p-himik 2024-01-06T11:14:41.528399Z

Oh, and since you rely on :required true, you should use checkValidity instead of re-checking the value yourself.

Ivana 2024-01-06T11:16:13.446809Z

If I move that style to root css file (or in header of index.html) it will be applyed to all the page again, right? And with form-2 I may have atoms with refs, ratoms with set/get value of input and avoid style magic & set :disabled on button directly πŸ™‚

Ivana 2024-01-06T11:17:32.064639Z

I'l google on checkValidity , thanks. Looks like it form's attribute/method, not input's one

p-himik 2024-01-06T11:18:17.243719Z

> it will be applyed to all the page again, right? Yes, but it won't be "technically incorrect" as with that :style and you'll be able to use a custom :class to help scope the value. There are libraries that do stuff like that at run time for you.

Ivana 2024-01-06T11:20:29.897289Z

Thanks, I'l create that root class for forms style, if could

πŸ‘ 1
Ivana 2024-01-06T12:56:10.875479Z

For now finished with css

form input:invalid ~ button {
  opacity: 0.4;
  pointer-events: none;
}
cljs
(defn- user-answer [right-answer on-success on-fail]
  [:form {:on-submit (fn [e]
                       (.preventDefault e)
                       (when-let [answer (some-> e .-target js/FormData. (.get "input") str str/trim not-empty)]
                         (if (= answer right-answer)
                           (on-success)
                           (on-fail)))
                       (-> e .-target .reset))}
   [:span "hint: " right-answer " "]
   [:input {:name "input"
            :required true
            :auto-complete :off}] " "
   [:button "Check answer"]])
Works fine, checks only non-empty & non-blank inputs, but resets blanks. I don't check form's validity cause anyway I have to extract input value, so it is enough to chek it only. Thanks alot, it's much better than my initial solution 😊

πŸ‘ 1