So it’s my first day of seriously trying to re-implement some of our old-school jQuery + Handlebars UI in Reagent. Due to some constraints, I currently have to mimic the current HTML/CSS structure we have.
I’ve hit my first snag that lead to some pretty hairy spaghetti code, so I hope there is a better way (I know re-frame, but I want to explore the basics first).
Imagine a reusable drop-down component. It is comprised by three elements:
- The top-level element,
:div.parent
- The button that toggles it,
:button
- The actual dropdown content
:div.content
The html structure looks roughly like so:
[:div.parent
[:button "Open"]
(when @is-open
[:div.content "More stuff here"])
Now due to my constraints, when the dropdown is open, I have to set a class to the :div.parent
element so it becomes :div.parent.open
. At the same time, because I want to be able to close via clicking outside, or hitting Escape I need to track some refs so I can distinguish between mouse clicks on the :div.content
(ignore), :div.parent
(ignore, and let the parent handle it) or the background.
I also need to pass around a function close-parent-fn
so that when something succeeds in the content (say you created a new widget), the dropdown closes and you don’t have to click outside. Or you could imagine an explicit close button etc etc.
This is all possible, yet a little bit hairy for a single-use component, but this general logic is reused throughout the application so I’d like to be able to separate the reusable bits (ref tracking, document event handling, etc etc) into some wrapper function, so in the end I could write something like:
(defn my-complex-content
[:div.content "imagine a huge thing here"])
...
[:some.pretty.nested.ui
[my-cool-dropdown
[:div.parent
[:button "Open"]
[my-complex-content]]]
I.e. something that shows intent rather than the plumbing.
I tried to implement this by wrangling vectors and mangling property maps, which kinda worked right up until I want to put something more complex in there (i.e. a function call).
If I understand the issue correctly, I’m suffering by a classic React problem, that is you have to thread some top-level concern (which function to call when you want to close the dropdown) through all intermediate components until you can attach it to some :on-click
handler.
I know React introduced Contexts for something similar to this, and they also mention render-props but I’m struggling to see how this maps to idiomatic Reagent.
Any pointers to documentation or projects that deal with this kind of thing in an elegant way would be very appreciated.