Fork me on GitHub
#reagent
<
2020-02-04
>
jaime16:02:10

How to properly handle nil checks when using ref From this example https://github.com/reagent-project/reagent/blob/master/doc/FAQ/UsingRefs.md

(defn video-ui []
  (let [!video (clojure.core/atom nil)]    ;; stores the
    (fn [{:keys [src]}]
      [:div
       [:div
        [:video {:src src
                 :style {:width 400}
                 :ref (fn [el]
                        (reset! !video el))}]]
       [:div
        [:button {:on-click (fn []
                              (when-let [video @!video] ;; not nil?
                                (if (.-paused video)
                                  (.play video)
                                  (.pause video))))}
         "Toogle"]]])))
I just noticed that I have to check that @!video is not nil everytime I access its properties.

p-himik16:02:14

What do you mean by "properly handle nil checks"? What's not proper about when-let?

jaime16:02:34

I'm thinking if I have to pass the !@video on other function I have to do the when-let everytime So I was thinking if there is a way to just do the check once.

p-himik16:02:04

The usages are all in different places, so you cannot do the check once.

p-himik16:02:26

But you can use some-> at some points.

p-himik16:02:44

E.g. (some-> @!audio .-duration (/ 60))

jaime16:02:11

Cool. I like it.

[:div {:class "absolute bg-blue" :width (str (percentage @!audio) "%") }]
Is there a way to get the percentage of else default to 0% here? Sorry to ask, I'm still learning most of the clojure functions

p-himik16:02:49

(some-> @!audio .-duration (or 0) (/ 60))

p-himik16:02:15

Wait, that's wrong.

p-himik16:02:41

It would short-circuit even before .-duration.

jaime17:02:44

Yeah, I guess I have to create util functions to reduce clutter

jaime17:02:50

Thanks a lot

ingesol10:02:23

For what it’s worth, the “ugliness” here can be solved by using https://github.com/mfikes/cljs-bean. That would give you idiomatic keyword access to your JS object, and no need for some->

jaime16:02:24

On my current component. notice I already used 3 when-let

(defn audio-player []
  (let [!audio (r/atom nil)]
    (fn []
      [:div {:class "mx-auto p-16"}
        [:div {:class "flex flex-1 items-center p-4 border border-gray-500 rounded-full relative"}
         [:div {:class "absolute bg-blue" :width (str (percentage @!audio) "%") }] ;; error since I forgot to check if its not nil
         [:button {:class "absolute"
                   :on-click (fn []
                               (when-let [audio @!audio]
                                 (if (.-paused audio)
                                   (.play audio)
                                   (.pause audio))))}
          "play"]]
        [:audio {:src "audios/sample.mp3"
                 :ref (fn [el]
                        (reset! !audio el))}]
       [:div {:class "flex flex-row flex-1"}

        [:div "Duration: " (when-let [audio @!audio] (/ (.-duration @!audio) 60))]
        [:div "Current Time: " (when-let [audio @!audio] (.-currentTime @!audio))]
        [:button {:on-click (fn [] (backward @!audio 5))} "Back"]
        [:button {:on-click (fn [] (forward @!audio 5))} "Forward"]]])))

p-himik16:02:54

If I'm not mistaken, with this code you won't get real-time values for the duration and current time. Because when the state of <audio> changes, there will be nothing that will trigger the re-render.

jaime16:02:47

Hmmmm. This two are being re-rendered. Because they deref the @!audio?

[:div "Duration: " (when-let [audio @!audio] (/ (.-duration @!audio) 60))]
        [:div "Current Time: " (when-let [audio @!audio] (.-currentTime @!audio))]

p-himik16:02:15

I think !audio should be changed only once the component is rendered for the first time. Usually, it doesn't change that often. But if it works fine, then I have no idea why.

jaime17:02:04

Will investigate this one :thumbsup:

lilactown19:02:32

I imagine you need to attach some sort of event handlers to the audio player, beyond just capturing the reference, to get the changes to the duration to update your UI

p-himik19:02:35

But Jaime said that it was updating even with the code above - that's what puzzles me.

jaime19:02:48

Yes, you guys are right. I need to add the event handler

(defn audio-player []
  (let [!audio (r/atom nil)
        state (r/atom {:currentTime 0})
        time-update-handler #(swap! state assoc :currentTime (.. % -target -value))]
    (r/create-class
     {:component-did-mount
      (fn [this]
        (some-> @!audio (.addEventListener "timeupdate" #(swap! state assoc :currentTime (.. % -target -currentTime)))))

      :reagent-render
      (fn []
        [:div {:class "mx-auto p-16"}
         [:div {:class "flex flex-1 items-center p-4 border border-gray-500 rounded-full relative"}
          [:div {:class "absolute bg-blue" :width (str (if !audio 0 (percentage !audio)) "%")}] ;; error since I forgot to check if its not nil
          [:button {:class "absolute"
                    :on-click (fn []
                                (when-let [audio @!audio]
                                  (if (.-paused audio)
                                    (.play audio)
                                    (.pause audio))))}
           "play"]]
         [:audio {:src "audios/sample.mp3"
                  :ref (fn [el]
                         (reset! !audio el))}]
         [:div {:class "flex flex-row flex-1"}

          [:div "Duration: " (when-let [audio @!audio] (.-duration @!audio))]
          [:div "Current Time: " (:currentTime @state)]
          [:button {:on-click (fn [] (backward @!audio 5))} "Back"]
          [:button {:on-click (fn [] (forward @!audio 5))} "Forward"]]])}
     )))

p-himik19:02:03

Ah, so it wasn't working before? :)

jaime19:02:44

It was working before, it updates the Duration even before event handling. Maybe because of when-let? The child was initially nil then ref was set and re-rendered?

[:div "Duration: " (when-let [audio @!audio] (/ (.-duration @!audio) 60))]

p-himik19:02:50

Ah, right, the duration is a one time thing.

p-himik19:02:02

I thought that the current time was also updating.

jaime19:02:07

Sorry. I meant the Current Time

jaime19:02:47

Yeah, I can confirm that below gets re-rendered

[:div "Current Time: " (when-let [audio @!audio] (.-currentTime @!audio))]

p-himik20:02:58

And this one I do not understand. If it works for some good reason (of which I have no clue), then you don't need any event listeners.

ingesol10:02:46

Either way, one might want to track the interesting values of the stateful JS component in a ratom of some sort, for proper reactivity. See this for a good explanation https://github.com/day8/re-frame/blob/master/docs/Using-Stateful-JS-Components.md

jaime19:02:16

thanks a lot. good read

mmeix19:02:11

Has anybody a simple example how to use react-spring in Reagent?