@lilactown I’m curious - would this type of stuff be easier with helix? This is kind of a toy project so I have freedom to just try other stuff and maybe it’d be a good excuse to take helix for a spin :)
helix does provide slightly easier support for using 3rd party JS
helix treats all components the same when creating elements:
(defn styled [type m]
(.styled stitches (name type) (clj->js m)))
(def blue-button (styled :button {:background "blue"}))
;; render it like any other component
($ blue-button {:id "special-blue-button"} "Click me!")
what it does not do is transform props into DOM-friendly names or anything. so you'll want to use camel-case event handlers and other such things:
($ blue-button {:onClick #(println "You clicked me!")} "Click me!")
I'm going to provide a helix.dom/$d
macro which does do that transformation for you for any component you give it, just haven't gotten around to it. maybe i'll make that my saturday morning project 🙂
@lilactown Is there any fundamental issue that requires these helpers? It would be much nicer (IMHO 😅) to just be able to call (blue-button ,,,)
but I’m realizing that helix’s $
and rum’s adapt-class
both don’t seem to aim for that?
two fundamental things IME. some of this you may know but I'll say it for good measure. 1. You don't want to call components directly as functions. Instead, we create elements which describe how to render the component. This lazy evaluation allows React to understand the boundary to encapsulate your component instance's state and lifecycle, and perform tricks like error boundaries, suspending/resuming rendering and time slicing. 2. You could create elements via higher-order-functions (as Rum and Fulcro does with their components), however this prevents us from doing compile-time analysis and optimizations.
That should go to a blog 🙂
in helix's case, helix creates React components. They accept JS objects as props and return React elements. so you need to convert the props map you pass in at element creation to a JS object (`$` does this at compile time), and then this gets reconverted to a map-like thing when you're in the body of the component (`defnc` does this at runtime, but very cheaply using cljs-bean)
Rum and other libraries navigate this conversion and reconversion problem not through compile-time tricks, but by passing in all of your CLJS data into one special prop that then your component plucks. so it looks more like this:
(react/createElement my-rum-component #js {:rum-props ,,,})
and then the rum component plucks its props out of that.this trick doesn't work on 3rd party React components, though, only those created by Rum. so you have to fall back to helpers that will convert your props map to a JS object in that case, which adapt-class
does
adapt-class
, like $
tries to do all of its work at compile time in order to avoid creating a bunch of maps, parsing them and then throwing them away during render
if you do want to have a blue-button
that you can call like a function, helix does provide a tool for that: "spread props" a la JSX
(defn styled [type m]
(.styled stitches (name type) (clj->js m)))
(def blue-button* (styled :button {:background "blue"}))
(defn blue-button
[props & children]
($ {:& props} children))
the special :&
keyword passed into the props map will cause $
to emit code to convert props
to a JS object at compile time and merge it in with any static props you've written. in this way, you can have your compile-time cake and eat some of it at runtime, too 🙂 at a glance, I didn't see this capability in rum/daiquiri
this isn't the default way of using helix because I like having the static props compilation whenever possible. helix also provides the ability to opt in to creating components as factory functions similar to rum and fulcro, but I don't know how much I want to support that going forward tbh