Fork me on GitHub
#re-frame
<
2022-11-18
>
DrLjótsson21:11:12

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?

DrLjótsson21:11:36

I.e., so that changes in sub-2 do not trigger a re-render.

p-himik22:11:24

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.

DrLjótsson22:11:07

Nice! Trying to locate the docs for :should-component-update ...

DrLjótsson22:11:40

{:should-component-update 
     (fn [this old-argv new-argv]
       false)}

DrLjótsson22:11:25

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?

DrLjótsson22:11:15

Or do I simply clear the state in :component-did-update before creating it again?

DrLjótsson22:11:23

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.

DrLjótsson22:11:47

When sub-1 changes that is.

DrLjótsson22:11:38

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....

DrLjótsson22:11:22

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.

DrLjótsson23:11:04

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?

p-himik06:11:57

Why do you return false from :should-component-update? You should return whether the sub-1 value has changed or not.

DrLjótsson06:11:18

No, that was just the signature of the function I was pasting in. I do return true if sub-1 changes

DrLjótsson06:11:30

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.

p-himik06:11:10

Can you post the code of the component, maybe without the render function?

DrLjótsson06:11:00

Let me clean it up a bit...

DrLjótsson08:11:42

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?

p-himik08:11:59

Yes, with React refs.

DrLjótsson08:11:29

Sorry, so it should not have been mounted.

DrLjótsson08:11:40

(.appendChild node (some-fn [:div class="foo" [:p "Some text"]]))

p-himik08:11:47

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.

DrLjótsson08:11:47

(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))})))

DrLjótsson08:11:18

Not easy to read the code I guess. But the 3rd argument tab-id is what should trigger the re-render.

DrLjótsson08:11:23

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

DrLjótsson08:11:06

The js-fn adds lots of elements to the node. And these elements are added again every time tab-id changes.

p-himik08:11:17

Ah. So on tab-id change, you want not to re-render, but rather to destroy and re-create the component?

DrLjótsson08:11:41

I guess so, yes!

p-himik08:11:02

Add ^{:key tab-id} in front of [dynamic-content-inner ... ... tab-id ... ...].

p-himik08:11:37

Maybe could be used in :reagent-render itself, but I'm 70% certain it's not the case.

p-himik08:11:24

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.

DrLjótsson09:11:34

Sir, you are a genius.

DrLjótsson09:11:11

How come the simplicity of a solution is often inversely related to the time spent on the problem?

DrLjótsson09:11:39

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