Fork me on GitHub
#reagent
<
2021-09-22
>
romdoq13:09:22

Hi folks, I'm using a component from an external JS/TS library, but the component expects its immediate children to be a particular type of other component, so it can inspect their props, etc. Eg:

<Foo>
  <Bar />
  <Bar />
</Foo>
However, I also want to wrap the Bar component's interface in something more clojure-y, yet doing so with plain hiccup adds an extra component layer between Foo and its Bars, which confuses the Foo component. Is there some reagent tool or technique to help with this, or do I just need to write a macro for it?

p-himik13:09:17

What do you mean by "more clojure-y"? Can you provide an actual code? > do I just need to write a macro for it? Not unless you really want for it to be handled in compile time. Hiccup is just vectors - you can compose it in run time just fine.

lilactown13:09:48

this is a case where a library like https://github.com/lilactown/helix might help:

[:> Foo
 ($ Bar)
 ($ Bar)]
you can mix in components created via helix into your reagent code with no other changes

lilactown13:09:19

helix.core/$ will take a map of props and convert it to a JS object for you, so you have a nice clojure-y interface

lilactown13:09:31

although, is there a reason :> isn't working?

romdoq14:09:28

When I say "more clojure-y", I mean having boolean props named things like active? rather than isActive. And so that the CLJS components are somewhat insulated from the (sometimes unintuitive) JS components' APIs. In this case, I was thinking of being able to have a similar component structure in CLJS as in the JS, eg:

...

(defn foo
  [props]
  [external-lib/Foo (->js-foo-props props)]
(defn bar
  [props]
  [external-lib/Bar (->js-bar-props props)]

...

(defn my-foo
  []
  [foo
    [bar {:active? true}]
    [bar {:active? false}]])
However, with the wrapper as plain hiccup, AFAICT React sees something more like:
<Foo>
  <myproject.foobar.bar>
    <Bar isActive />
  </myproject.foobar.bar>
  <myproject.foobar.bar>
    <Bar />
  </myproject.foobar.bar>
</Foo>

romdoq14:09:06

My current approach is to just pass a collection of bar props maps to the wrapped container component, which then deals with the external lib internally:

(defn my-foo
  []
  [foo {:bars [{:active? true}
               {:active? false}
    

p-himik15:09:19

My first recommendation would be to avoid renaming properties just to make them look nice. The property names might look more "Clojure-y", but the approach itself is not. In general, the interop is embraced, not tucked away. But if you still want to do that, I would try to make sense of what exactly Foo wants from its children. If you can do that in a reasonable time and can easily adapt your wrapper to do that as well, then all is good. Finally, you can use what lilactown suggests with $ or turn bar from a view function into a Hiccup-creating function - you'd use it with () instead of [], thus preventing a new intermediate component from appearing. But there are downsides (might or might not be applicable to your wrapper - depends on its code) which are described here: https://cljdoc.org/d/reagent/reagent/1.1.0/doc/tutorials/using-square-brackets-instead-of-parentheses-#the-difference-between--and

romdoq15:09:36

Yeah, I tried a solution with (), and it worked, but the requirement to remember to use () for this particular case is IMO likely to lead to mistakes slipping through, especially as () and [] look so similar. I'm a strong believer in having explicit boundaries between concerns of an app, so I've been wrapping external components to minimise the impact of the API changes of our company's internal JS components. Our architecture has advanced significantly from the project's early days though, so it may be time to revisit the decision. :thinking_face:

romdoq15:09:42

Thanks for the advice! 🙂