This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2020-11-26
Channels
- # announcements (3)
- # babashka (28)
- # beginners (21)
- # cider (29)
- # clojars (10)
- # clojure (14)
- # clojure-australia (3)
- # clojure-europe (48)
- # clojure-nl (3)
- # clojure-sanfrancisco (4)
- # clojure-uk (54)
- # clojurescript (34)
- # cryogen (12)
- # cursive (7)
- # datomic (14)
- # devcards (1)
- # fulcro (23)
- # helix (2)
- # java (5)
- # jobs (1)
- # kaocha (15)
- # malli (13)
- # minimallist (1)
- # off-topic (8)
- # pathom (7)
- # pedestal (1)
- # rdf (10)
- # reagent (18)
- # shadow-cljs (58)
- # spacemacs (3)
- # tools-deps (1)
- # vim (6)
- # xtdb (37)
Hi all. I'm at a loss of how to properly make a higher-order component work. I want to have a component that wraps others, adding/overriding props on its children. I notice that the rendered output still uses the props from the original child, which hints to me that reagent is rendering bottom-up (child first, parent later). However, for my purposes I would like to get a chance for the parent to augment the props of its child before it gets rendered. How should I go about that? (I can provide code snippets if my explanation isn't clear enough)
(defn- bind-component [component]
[:> (oget form-context "Consumer") {}
(fn [context-namespace]
(reagent/as-element
[:> (oget form-attribute-context "Consumer") {}
(fn [context-attribute]
(reagent/as-element
[component {:namespace (context-value->keyword context-namespace)
:attribute (context-value->keyword context-attribute)}]))]))])
(defn label [props & children]
(bind-component (fn [{:keys [namespace attribute]}]
(into [:label (merge props
{:for (namespaced-attribute->id namespace attribute)})]
children))))
(defn input-binding [{namespace-from-props :namespace attribute-from-props :attribute} & children]
(bind-component (fn [{namespace-from-context :namespace attribute-from-context :attribute}]
(let [namespace (first (filter some? [namespace-from-props namespace-from-context]))
attribute (first (filter some? [attribute-from-props attribute-from-context]))
mapped-children (map-indexed
(fn [idx [type props & children]]
(println "mapping child of input-binding" type props)
(into [type
(merge props
{:id (str (namespaced-attribute->id namespace attribute) (when (not= idx 0) (str "." idx)))
:value @(subscribe [::subs/attribute-value namespace attribute])
:on-change (fn on-bound-input-change [value & _anything]
(println "synchronously dispatching change-atrribute-value of bound input" namespace attribute value)
(dispatch-sync [::events/change-attribute-value namespace attribute value])
(when (fn? (:on-change props))
(println "calling custom on-change")
((:on-change props) value)))
:on-blur (fn [e] (dispatch [::events/implicitly-confirm-attribute-value namespace attribute (oget e "target.?value")]))})]
children))
children)]
(println "mapped children:" mapped-children)
(into [:div {:title (namespaced-attribute->id namespace attribute)}]
mapped-children)))))
;; and then in some other component:
(defn other-component [search]
[input-binding nil
[date-picker {:on-change search}]])
I realize all the namespace-stuff is obfuscating the problem, but since I'm afraid it might be related to the problem I decided to leave it in...
Too much is going on in your code for me to efficiently find the problem.
E.g. have you tried reproducing it without that strange bind-component
?
Also, just in case - (first (filter some? [a b]))
can be replaced with just (or a b)
, unless a
can be false
.
I'll come up with a smaller reproduction 😉 Might take few minutes though...
But I'll say this in advance - your initial assumption that you can just "patch" children is correct. You can absolutely do that, assuming that it's the parent component that gets re-rendered. If you manage to change the "patching" process in such a way that doesn't actually re-render the parent component, then it will not work.
do I need to do anything in particular to make the parent re-render? Or is it sufficient to ensure that the new children are not identical to the originals?
I have a background in react and cljs/reagent is newer to me, so I'm sometimes unsure which of my knowledge translates over 😉
Reagent is just a wrapper over React with some ratom magic. Since you're not using ratoms above, there's no magic. Imagine you get a child React element in a component and make that component create a new element based on it. Same thing.
As one would expect, works just fine:
(ns clj-playground.core
(:require [reagent.dom]
[clojure.browser.dom :as dom]))
(defn parent [{:keys [extra-value]} & children]
(into [:div]
(map (fn [[child-component child-props & grandchildren]]
(into [child-component (assoc child-props :extra-value extra-value)] grandchildren)))
children))
(defn child [{:keys [value extra-value]} & children]
(into [:div
[:span "Value: " value]
(when extra-value
[:<>
[:br]
[:span "Extra value: " extra-value]])]
children))
(defn app []
[parent {:extra-value "there"}
[child {:value "Hello"}]
[child {:value "You"}]])
(defn ^:export init []
(reagent.dom/render [app] (dom/get-element "app")))
Ok, thanks for assuring me that the rpincipal idea should work. Now I'll go back and dig into why my complicated specific case is not behaving 😛
What is the point of reagent.core/rswap!
? I don’t quite understand the docstring, and I don’t get when you’d want to use it over swap!
.
I suppose regular swap!
has some issues with the code like this:
(swap! a (fn [v] (swap! a 1) (inc v)))
.
I see. And I agree. 🙂 Thanks! @U2FRKM4TW