Need to wait for Reagent to complete rendering an element before measuring its size?

In a clojurescript project, I have an svg element represented as hiccup and I want to measure it’s exact size. The way I’m doing it is to render the element using reagent.dom/render and then measure the element using getBBox (https://developer.mozilla.org/en-US/docs/Web/API/SVGGraphicsElement/getBBox). As soon as I’ve measured its size I remove the element from the dom because I don’t need it, I just want to know what its dimensions would be when rendered

Here’s my code. It seems to work fine, but what I’m wondering is, am I creating a race condition where .getBBox could be called before reagent.dom/render has finished rendering the SVG? If so, how can I avoid this race condition?

(defn measure-svg-element
  "Given an svg element has hiccup, measure its size precisely
   by rendering it and using getBBox. Returns a map with keys
   x, y, width, height"
  [element-hiccup]
  (let [el-id (str "el-" (random-uuid))
        div-id (str "div-" (random-uuid))
        svg [:svg
             {:xmlns "http://www.w3.org/2000/svg"
              :id el-id}
             element-hiccup]
        body (-> js/document (.querySelector "body"))
        div (-> js/document (.createElement "div"))]
    (-> div (.setAttribute "id" div-id))
    (-> body (.append div))
    (rdom/render svg
                 (-> js/document (.getElementById div-id)))
    (let [el (-> js/document (.getElementById el-id))
          bbox (.getBBox el)
          dimensions {:width (.-width bbox)
                      :height (.-height bbox)
                      :x (.-x bbox)
                      :y (.-y bbox)}]
      (rdom/unmount-component-at-node (-> js/document (.getElementById div-id)))
      (-> div (.remove))
      dimensions)))

Try using refs instead: https://github.com/reagent-project/reagent/blob/master/doc/FAQ/UsingRefs.md

1 Like

Thanks, I wasn’t aware of refs in reagent.

As an option, you could manually add a div to your markup outside your “app” area, so React doesn’t change it. Make it static and invisible, so it doesn’t interfere with your other markup at all. And use this div to inject the generated svg’s and get their size. You could use Hiccups for Hiccup -> HTML, and use Dommy to inject this generated svg string into the hidden div and get the size.

Refs are great when you need to get the size of an element which stays on the page. But in your case you need to remove it after rendering.

Thanks, I ended up doing something along those lines. I used hickory to convert the hiccup to string, although I might change to the hiccups library you recommended. I used js interop to made document fragments from strings with .createContextualFragment and then injected them into the page dom with .append

I started down the route of using reagent for the rendering of the temporary div but it’s a pain because the rendering is async, and I couldn’t figure out how to make a function that returns the actual value of the size of the rendered svg, instead of just a promise or a callback or a core.async channel.

For rendering hiccup to string, I realised that a better option for me is reagent.dom.server/render-to-static-markup which has the advantage that it accepts reagent-style hiccup, i.e. with nested attribute maps like

[:div
 {:style {:color "red"
          :background-color "blue"}}
 "hello world"]

which you can’t use with hiccups or hickory.

2 Likes