Fork me on GitHub
#reagent
<
2022-02-21
>
gammarray15:02:03

I'm working with react-native-maps and trying to perform an operation on a marker. I want to use this.refs[markerIdentifier].showCallout() , but I can't seem to figure out where to access this.refs within reagent. Searching back in Slack, I see someone asked before and got a recommendation to use (r/dom-node this), but when I try that I get TypeError: undefined is not an object (evaluating 'reagent.core.dom_node.cljs$core$IFn$_invoke$arity$1'). Anyone got ideas on what I should try next?

p-himik15:02:40

Where did you get this.refs[markerIdentifier].showCallout(), is it documented anywhere?

gammarray15:02:26

Aha, I just realized my r in r/dom-node was referring to reagent.core while dom-node is part of reagent.dom

p-himik15:02:07

Hm, doesn't seem like there's a proper example. Don't use r/dom-node unless there's absolutely no other way to do what you want to do. And in this case, you definitely don't need the DOM node - you need the underlying JS object that ends up rendering that node. Try using this-as within the relevant function and regular JS interop to access the refs attribute, the relevant index within it, and to call the right function on it.

gammarray15:02:36

I have the map ref, but that doesn't contain the data I need to work on.

gammarray15:02:10

I'm not familiar with this-as yet. Looking it up now...

p-himik15:02:50

(this-as this ...) basically gives you the this object that you have in JS.

p-himik15:02:00

But in CLJS you can bind it to any name and not just this.

gammarray15:02:34

Ah, thanks, I'll give that a try

juhoteperi15:02:46

dom-node is only available with React-dom, it doesn't exist on React-native

neumann22:02:59

I'm seeing strange behavior with :ref. Anytime the component changes, the callback is called with nil and then immediately called with the same DOM element again. Here's a minimalist example:

(defn test-update-ref []
  (let [local-state (r/atom nil)
        dom-ref (atom nil)]
    (js/console.log "creating instance")
    (fn []
      (js/console.log "rendering")
      (js/setTimeout (fn [] (reset! local-state "Foo Bar")) 2000)
      [:div {:ref (fn [el]
                    (js/console.log "ref" el)
                    (when el
                      (js/console.log "identical?" (identical? @dom-ref el) @dom-ref el)
                      (reset! dom-ref el)))}
       @local-state])))
Which logs out:
creating instance
rendering
ref div
identical? false null div
rendering
ref null
ref <div>Foo Bar</div>
identical? true <div>Foo Bar</div> <div>Foo Bar</div>
What's going on? Anyone know why the ref would be called twice in a row when the DOM element is clearly being mutated by React?

neumann22:02:42

I'm using reagent 1.1.0, react 17.0.1, and react-dom 17.0.1

neumann22:02:36

Ah ha! I figured it out. The anonymous function being passed to :ref is the issue. It must be included in props comparison, so props equality will fail since it's the refs function is reallocated every render call. Keeping the function reference stable ensures the ref callback is only called once:

(defn test-update-ref []
  (let [local-state (r/atom nil)
        dom-ref (atom nil)
        handle-ref (fn [el]
                     (js/console.log "ref" el)
                     (when el
                       (js/console.log "identical?" (identical? @dom-ref el) @dom-ref el)
                       (reset! dom-ref el)))]
    (js/console.log "creating instance")
    (fn []
      (js/console.log "rendering")
      (js/setTimeout (fn [] (swap! local-state #(or % "Foo Bar"))) 2000)
      [:div {:ref handle-ref}
       @local-state])))

lambder08:02:50

or declare this:

lambder08:02:52

(extend-protocol IEquiv
  MetaFn
  (-equiv [this other]
    (if-not (instance? MetaFn other)
      false
      (= (-> this meta :equiv) (-> other meta :equiv)))))

lambder08:02:56

and add meta to your anonymous functions (withmeta #(...) {:equiv :some-discriminator-value})

lilactown16:02:48

I would definitely not do that. that would change the equality semantics of every function with metadata in your app, which is likely to have unforeseen consequences

neumann17:02:25

For some reason I was under the misimpression that :ref was treated specially and would not be in the props comparison. Now that I understand how it behaves, I can see the rationale of calling the old function reference with nil (so it can clean up), and then the new function reference with the element (so it can set up). If the function reference changes, how is Reagent or React to know if it is functionally different or not? Thus, it has to transition the function reference. It's definitely something to watch out for.

1
lambder14:02:39

it can be improved to change the semantics of function which have :equiv key defined on its metadata

lambder14:02:03

that would be strong enough intention to have the semantics changed

lambder14:02:56

(extend-protocol IEquiv
  MetaFn
  (-equiv [this other]
    (if-not (instance? MetaFn other)
      false
      (and 
(-> this meta :equiv some?)
(-> other meta :equiv some?)
(= (-> this meta :equiv) (-> other meta :equiv))))))