-
-
Notifications
You must be signed in to change notification settings - Fork 715
Container Component Macro Ideas
Josh Freckleton edited this page Mar 9, 2017
·
2 revisions
This is a macro inspired by the discussion under Testing Components 2c and the linked blog posts/talks etc.
With one def
form it creates two functions, one is the container and sets up the appropriate subscriptions, the other is the actual component.
While I am currently using this macro in one of my projects, I think this usage pattern is sub-optimal since it assumes a 1:1 correspondence between containers and components. Based on the talk by the facebook architect, this may not be an ideal assumption to make. However, I think it covers probably 40% of the form-2 components that I create. The implementation is also fairly... decoupled. Possibly overly so.
(defmulti valid-spec?
"Validate whether this subscription spec is valid."
class)
(defmethod valid-spec? :default [spec] false)
(defmethod valid-spec? clojure.lang.Symbol [spec] true)
(defmethod valid-spec?
clojure.lang.PersistentVector
[spec]
(= 2 (count spec)))
(defmethod valid-spec?
clojure.lang.PersistentUnrolledVector
[spec]
(= 2 (count spec)))
(defprotocol SubscriptionSpec
(subscription-symbol [spec] "Extract the subscription binding symbol from this spec.")
(subscription-key [spec] "Extract the subscription key from this spec."))
(extend-protocol SubscriptionSpec
clojure.lang.Symbol
(subscription-symbol [spec] spec)
(subscription-key [spec] [(keyword spec)])
clojure.lang.PersistentVector
(subscription-symbol [spec] (first spec))
(subscription-key [spec] (second spec)))
(defn subscription-binding [subscription-spec]
(let [generate-spec (fn [spec]
`[~(subscription-symbol spec)
(re-frame.core/subscribe
~(subscription-key spec))])]
(if (valid-spec? subscription-spec)
(generate-spec subscription-spec)
(throw (ex-info "Invalid subscription spec. Must be a symbol or a two-element vector."
{:spec subscription-spec})))))
(defn container-name [component-name]
(symbol (format "%s-container"
(name component-name))))
(defmacro defcomponent-2 [name subscriptions & body]
`(do
(defn ~name ~subscriptions
~@body)
(defn ~(container-name name) []
(let [~@(mapcat subscription-binding subscriptions)]
(fn []
[~name ~@(map (fn [sub] `(deref ~(subscription-symbol sub))) subscriptions)])))))
Deprecated Tutorials:
Reagent: