This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-11-18
Channels
- # aws (1)
- # babashka (35)
- # beginners (52)
- # biff (4)
- # calva (55)
- # cider (19)
- # clojure (54)
- # clojure-dev (3)
- # clojure-europe (23)
- # clojure-nl (1)
- # clojure-norway (3)
- # clojure-uk (2)
- # clojurescript (9)
- # code-reviews (3)
- # datahike (1)
- # fulcro (1)
- # funcool (4)
- # graalvm (21)
- # gratitude (2)
- # java (5)
- # jobs (2)
- # joyride (1)
- # kaocha (13)
- # malli (2)
- # off-topic (22)
- # other-languages (11)
- # pathom (4)
- # re-frame (35)
- # reagent (3)
- # reitit (3)
- # releases (2)
- # remote-jobs (1)
- # rum (1)
- # shadow-cljs (42)
- # sql (18)
- # tools-deps (72)
- # web-security (6)
- # xtdb (15)
I am wrapping an external stateful JS component using a level-3 component. The component is dependent on two subscriptions. However, I only want it to update if sub-1 changes and only then retrieve data from sub-2. Any quick advice on how to achieve that?
I.e., so that changes in sub-2 do not trigger a re-render.
Make that component agnostic of re-frame by creating a wrapper and passing the right data from the subscriptions to that component.
Then, implement :should-component-update
and make it return true
only when the sub-1 value changes.
Nice! Trying to locate the docs for :should-component-update
...
{:should-component-update
(fn [this old-argv new-argv]
false)}
OK, next problem. I want the stateful component to completely re-render when sub-1 changes. I tried using the same function for :component-did-mount
and :component-did-update
but the state from :component-did-mount
is still there and :component-did-update
only "adds" to it. Is this where I should use forceUpdate
?
Or do I simply clear the state in :component-did-update
before creating it again?
Nope, can't do that... So what i need is not a totally clean slate. I want the component in the state that :reagent-render
returns and then to basically run :component-did-mount
again.
When sub-1 changes that is.
I tried (.forceUpdate this)
in :`should-component-update` when sub-1 changes but that does not trigger a full re-render, :reagent-render
is called (which does not depend on the value of sub-1), but :component-did-mount
is not called. Feels like there is something about react that I am not grasping here....
Perhaps more context is useful. The form-3 component wraps a javascript function that manipulates the DOM. So :reagent-render
sets up the DOM node, and :component-did-mount
dispatches the DOM node to the js function, which adds lots of stuff to it, using data from sub-2, i.e., (js-fn node sub-2-data)
. If sub-2 changes, the js fn does not need to be run again. However, if sub-1 changes, the DOM node should be reset and the js fn should be called with new sub-2 data.
Do I need to unmount and remount the form-3 component? Double-wrap it? That is, an outer form-3 component that keeps track of changes in sub-1 and an inner component that is unmounted and mounted again when sub-1 changes?
Why do you return false
from :should-component-update
? You should return whether the sub-1 value has changed or not.
No, that was just the signature of the function I was pasting in. I do return true if sub-1 changes
But the problem is that if I run (js-fn node new-sub-2-data)
in :component-did-update
when sub-1 changes, js-fn gets the "previous" node and keeps adding elements to that. Instead of a fresh one.
Let me clean it up a bit...
I think I am my way to finding a solution. Is there a way to get back a DOM element from hiccup? That is, [:div class="foo" [:p "Some text"]]
->
an element that I can append to a node?
Sorry, so it should not have been mounted.
(.appendChild node (some-fn [:div class="foo" [:p "Some text"]]))
I feel like you're moving in a direction you don't actually wanna go. In part because I don't know of an easy way of doing that, in part because I've never seen any need for it in many years of interoping with JS libraries, and also because you'd be basically marrying React with imperative DOM manipulation.
Ok ok. 🙂
(defn dynamic-content-inner
[{:keys [fsm-id state-key]} {:keys [tag attrs content element-options]} tab-id js-content-data update-content-data!]
(println "BOOM! inner first call")
(let [render (fn [comp]
(let [js-fn (:data-dynamic-render-fn attrs)
node (reagent.dom/dom-node comp)
{:keys [current-ns]} element-options
update-content-data! (get (r/argv comp) 5)]
(js-src-mount/call js-fn node js-content-data update-content-data! current-ns)))]
(r/create-class
{:component-did-mount render
:component-did-update render
:should-component-update
(fn [this old-argv new-argv]
(not= (get old-argv 3) (get new-argv 3)))
:reagent-render
(fn [content-options {:keys [tag attrs content element-options]} tab-id js-content-data update-content-data!]
(into [(keyword tag) attrs] content))})))
Not easy to read the code I guess. But the 3rd argument tab-id is what should trigger the re-render.
The problem is that when I run render
in :component-did-update
`, the node that`js-fn` gets is the node manipulated by :component-did-mount
`, whereas what I want is a fresh node as created by :reagent-render
The js-fn
adds lots of elements to the node. And these elements are added again every time tab-id changes.
Ah. So on tab-id
change, you want not to re-render, but rather to destroy and re-create the component?
I guess so, yes!
Maybe could be used in :reagent-render
itself, but I'm 70% certain it's not the case.
Alternatively (e.g. if the component ends up flickering or doing unnecessary computations every time it's mounted), you can try and simply clear the node before adding extra stuff to it.
Sir, you are a genius.
How come the simplicity of a solution is often inversely related to the time spent on the problem?
re clearing the node, that was my idea with the hiccup -> a mountable element. Because I can't clear the node entirely, it contains some elements (now created in :reagent-render
that js-fn needs. So I was thinking of clearing the node and adding the hiccup before calling js-fn in :component-did-update