Clojurescript is the sister language of Clojure which runs on Javascript.

It brings all the niceties of Clojure to web development which is quite nice. It's also able to share code between Clojure and Clojurescript making it quite a handy tool for hybrid applications.

A common way of doing frontend in Clojurescript is to use React and its wrapper Reagent. While this is nice, the same way using bare React is not recommended nowadays, using bare reagent is quite barebones.

A library called Re-frame brings the gap by adding a way to do events like in React with Flux.

Here is my component :

(defn audio-player [_name]
  (let [global-volume @(rf/subscribe [:global-volume])
        src @(rf/subscribe [:audio-src])]
    (r/create-class
     {:component-did-mount
      (fn []
        ;; Initialize volume and source when the component mounts
        (when-let [audio-element @audio-ref]
          (set! (.-volume audio-element) global-volume)))

      :component-did-update
      (fn [_this]
        (when-let [audio-element @audio-ref]
          ;; Sync volume when it changes
          (rf/dispatch [:sync-volume global-volume])
          ;; Update source if it changes
          (when-not (= (.-src audio-element) src)
            (.play audio-element)))) ;; Auto-play the new source

      :reagent-render
      (fn []
        [:audio
         {:ref       #(reset! audio-ref %)
          :controls  true}])})))

Running the dispatch :sync-volume works in :component-did-update but doesn't in :component-did-mount due to it being too early (I guess?)

re-frame: no :event handler registered for:
Object { ns: null, name: "sync-volume", fqn: "sync-volume", _hash: 813487799, "cljs$lang$protocol_mask$partition0$": 2153775105, "cljs$lang$protocol_mask$partition1$": 4096 }

Setting the volume by hand fixes this for early mount, then the event works for the rest.