re-frame

jlfischer 2026-03-17T19:01:21.818839Z

In my application I need to add a click handler to the document, outside of any rendering roots, and that handler needs to look something up in re-frame.db/app-db. (It calls preventDefault on the event in certain cases.) I know I'm not supposed to deref a Reagent atom outside of a component, but at the moment it looks like things are working ok doing just that. Is there any specific cleanup I should be doing after deref-ing to make this safe?

;; Top-level click listener for the document. I use this to close the
;; navbar menus if you click outside of them.
(defn document-click
  [^js e]
  (let [db @re-frame.db/app-db]
    (when (navbar/should-close? db e)
      (.preventDefault e)
      (.stopPropagation e)
      (rf/dispatch [::navbar/close]))))

vanelsas 2026-03-18T08:10:22.982869Z

another (slightly more complex) solution would be to write a small hook that that takes care of closing elements based on clicks, escape, or when a menu item is clicked etc). The hook can be added to a click-away-component that wraps whatever you want to close on outside/inside clicks. No need to touch the app-db this way. Something like this:

(ns app.components.generic.hooks.use-click-away
  (:require
    ["react" :refer [useEffect useRef]]))


(defn use-click-away
  "Hook-style utility. Returns a `ref` to attach to an element. Dispatches `close-event` on:
      - outside click,
      - Escape key,
      - internal click on element with `data-close-on-click`."
  [close-fn]
  (let [ref (useRef nil)]
    (useEffect
      (fn []
        (let [handle-click-away (fn [e]
                                  (when (and (.-current ref)
                                             (not (.contains (.-current ref) (.-target e)))
                                             (not (.closest (.-target e) ".modal")))
                                    (.stopPropagation e)
                                    (close-fn)))

              handle-escape (fn [e]
                              (when (= "Escape" (.-key e))
                                (.stopPropagation e)
                                (close-fn)))

              handle-inner-click (fn [e]
                                   (when (.hasAttribute (.-target e) "data-close-on-click")
                                     (.stopPropagation e)
                                     (close-fn)))]

          ;; Attach listeners
          (.addEventListener js/document "mousedown" handle-click-away)
          (.addEventListener js/document "keydown" handle-escape)
          (when (.-current ref)
            (.addEventListener (.-current ref) "click" handle-inner-click))

          ;; Cleanup
          (fn []
            (.removeEventListener js/document "mousedown" handle-click-away)
            (.removeEventListener js/document "keydown" handle-escape)
            (when (.-current ref)
              (.removeEventListener (.-current ref) "click" handle-inner-click)))))
      #js [(.-current ref)]) ; Only re-run effect if ref changes

    ;; Return ref to attach to DOM element
    ref))

🙌 1
DrLjótsson 2026-03-18T08:13:01.377109Z

"Right, but once I leave that event handler it's too late to mutate it.". You are right. I would just dereference the db-atom, like you are doing.

DrLjótsson 2026-03-18T10:16:13.621259Z

@alexander.vanelsas this is fascinating ! How does one attach this to an element?

Kimo 2026-03-18T10:27:05.577109Z

You could also do it without hooks, using a react functional ref:

(defn my-component []
  (let [this-ref   (atom nil)
        reset-ref! #(reset! this-ref %)]
    (reagent/create-class
     {:component-did-mount    #(add-my-listeners! @this-ref)
      :component-will-unmount #(remove-my-listeners! @this-ref)
      :reagent-render
      (fn []
        [:div {:ref reset-ref!}])})))

vanelsas 2026-03-18T11:37:16.984519Z

@brjann one way to do it is to define a simple click-away-listener that uses the hook, and wrap it around the component that you need this behaviour for, something like this:

(ns app.components.generic.click-away-listener.views
  (:require
    [app.components.generic.hooks.use-click-away :refer [use-click-away]))


(defn click-away-listener
  "Wraps any UI component. Runs `close-fn` if user clicks outside, presses Escape, or if the Element has a `data-close-on-click` set to true (E.g. a menu-item)."
  [{:keys [close-fn child]}]
  (let [hook-ref (use-click-away close-fn)]
    [:div.click-away-listener {:ref hook-ref}
     child]))

vanelsas 2026-03-18T11:41:32.622149Z

You can then call it anywhere like this (pseudo code)

[:f> click-away-listener {:child [a div or component here that you want to close if clicked outside]
                                                                :close-fn #(dispatch [some event you need here to close the component)}]

DrLjótsson 2026-03-17T21:27:16.473349Z

The re-frame way would be to put the logic in the dispatch which receives the event. Purist would also put the event mutation in an :fx

jlfischer 2026-03-17T22:10:36.315199Z

Right, but once I leave that event handler it's too late to mutate it. I guess I could dispatch-sync a re-frame event that includes the DOM event and handle the mutation within re-frame that way.

wevrem 2026-03-17T22:32:31.716189Z

Why does it have to be outside of rendering? Something is happening to make the navbar menu appear, can you install the listener at that point? That is the point where you could subscribe to navbar/should-close?

jlfischer 2026-03-17T22:40:59.265549Z

The coeffect handler that sticks the current app-db value in the re-frame event handler context simply dereferences the app-db ratom, so I guess it's not a big deal: https://github.com/day8/re-frame/blob/master/src/re_frame/cofx.cljc#L42

jlfischer 2026-03-17T22:41:59.649899Z

I prefer registering a single listener at startup time for simplicity's sake: I don't have to juggle whether or not that listener is active, it's just always there.

Larry Jones 2026-03-17T21:26:42.521009Z

I am working to understand re-frame as an application development tool. What would community members recommend for learning this package and also for understanding it in the bigger “software engineering” perspective? Thanks.

Larry Jones 2026-03-18T12:51:47.496509Z

Thanks, Vincent. I’ve been reading this set of posts. Only half-way through. Valuable background, but so far, not “scratching my itch.” (Probably just me.)

2026-03-17T21:52:36.274649Z

Not sure if it is what you want, but maybe read https://day8.github.io/re-frame/re-frame/

🎯 1