Fork me on GitHub
#clojurescript
<
2019-07-01
>
danielneal13:07:21

@geo.ciobanu if your aim is to go from hoplon -> native, you could also keep an eye on what the react native team are up to with their rearchitecting, "Fabric". It sounds like it will be a lot more direct: > In the new system, the UI operations are directly exposed to JavaScript as functions using the JSI interface described above. The new UI manager can then create ComponentDescriptors and the Shadow Nodes for specific view types (like Text, View or Images), and then communicate with Java/ObjC to draw platform specific UI. from http://blog.nparashuram.com/

George Ciobanu19:07:18

@danieleneal thank you! Hoplon is a parallel path to React (though similar in some ways). I'll check it out though tbh I prefer the Flutter model performance-wise.

danielneal19:07:37

Yes I'm mainly thinking that you might be able to use some of the 'bits' rather than the react stuff. React native 2 is a long way off done afail but you could possibly connect their work on JSI - the new javascript to native interface that their working on - directly to hoplon's cells

George Ciobanu05:07:10

Apologies for the late reply, I was traveling. Thank you again for this idea. I need to look into React 2 more, perhaps my performance quandaries are quelled. As for JSI, again I'll have to look into it. Grateful for your thoughts

danielneal07:07:57

no worries - I don’t even know if there’s enough there to work with yet - I’ve only seen a talk and a few blog posts, but there might be some ideas there worth keeping an eye on

👍 4
fadrian21:07:19

I've written a reagent app that's not working. I have a render function that renders a set of lines. Each line has a set of edit fields whose backing atoms are held in a map tagged by field name. The line render function looks like this: (defn render-line [line] ^{:key (generate-id)} [:div {:style {:vertical-align "bottom"}} (render-fields line)])

fadrian21:07:40

and render-fields looks like this:

fadrian21:07:41

(defn render-field [line name-kwd val] ^{:key name-kwd} [:span {:style {:vertical-align "bottom"}} (render-h-space) [:font {:size "1"} (str name-kwd)] (render-text-field (str (:line-id line) ":" (str name-kwd)) val)]) (defn render-fields [line] (doall (for [[k v] (:bindings line)] (render-field line k v)))) render-text-field is this: (defn atom-backed-textfield [id value] [:input {:id id :value @value :type "text" :on-change #(change-textfield value %)}]) (defn render-text-field [id val] (atom-backed-textfield id val))

fadrian21:07:13

The problem is that when I re-render the line, the input field that is being edited is being created anew and the focus is lost in the edit field. How can I structure my render code so that I do not need to recreate the edit fields on the re-render of a line?

fadrian21:07:48

I type a character in an edit field. This triggers a call to change-textfield, which changes the value of the atom backing the edit field. This causes the line to be re-rendered, which causes the creation of a new edit field which does not have focus. As such, one can only type one character at a time before having to re-click in the edit field to regain focus. I'm sure that there's something I'm not seeing about render functions here, but the issue seems generic - how does one change a value in an atom backing a widget without reagent triggering a re-rendering of that input field, losing focus in the process?

lilactown23:07:42

the first thing I notice is that you’re using :key wrong

lilactown23:07:11

and the way you’re using components is also wrong

lilactown23:07:56

when you want to render a reagent component, for example atom-backed-textfield, you should put it in a vector, not call it like a function

lilactown23:07:16

e.g.:

(defn render-text-field [id val]
  [atom-backed-textfield id val])

lilactown23:07:48

you should change all of the places you’re calling components like a function, to be in a vector instead

lilactown23:07:03

this way, reagent can wire up dereferences correctly

lilactown23:07:28

at the moment, this will render in reagent as one huge hiccup tree, that will re-render every single component when the atom changes

lilactown23:07:55

which might be why you’re losing focus in your input field

lilactown23:07:53

for adding a key to a component, that should be done at the site of enumeration, not within an individual component’s body

lilactown23:07:18

so in this case, in render-fields you should have:

(defn render-fields [line]
  [:<>
    (for [[k v] (:bindings line)]
      ^{:key k} [render-field line k v])])

lilactown23:07:43

hope that helps

fadrian23:07:34

That sounds good. I'll try that. I understand the need for keys... I've seen that video before, looking for info during earlier attempts to use reagent. But I don't understand the difference between having the key insertion where I currently have it and moving it outside the function call.

fadrian23:07:26

Actually, thinking about it a bit, I may see what's going on.

lilactown23:07:52

if we ignore react a minute, Reagent is supposed to work like this:

(defn A [n]
  [:div n])

(defn B []
  [A 1])

(reagent.core/render [B])
Reagent expects a hiccup vector like [B], and then calls that function. that function should return hiccup. the first item in a reagent hiccup vector can be a keyword like :div, signaling a native element, or another component like A. if it sees another component A, Reagent will call it with the props you give it and expect it to return some hiccup... and so on.

lilactown23:07:44

so it will traverse the tree: - render [B] - call B, render [A 1] - call A with 1 as an arg, render [:div 1] - stops

fadrian23:07:04

And so we're attaching the keys to...?

fadrian23:07:25

The hiccup vector?

fadrian23:07:52

Actually scratch that - it's hiccup all the way down. I do think I see now what's going on.

lilactown23:07:10

yes it needs to be hiccup all the way down

lilactown23:07:31

each hiccup acts kind of like a “barrier”, where anything below it can re-render without re-rendering the entire application

lilactown23:07:32

e.g.:

(defn A []
  (let [n (r/atom 0)]
    (fn []
      [:div n [:button {:on-click #(swap! n inc)}]]))

(defn B1 []
  [:div [A]]) ;; this will only re-render the `A` component

(defn B2 []
  [:div (A)]) ;; this will re-render everything including the `B2` component

lilactown23:07:10

keys help Reagent know if a specific item in a list was deleted or updated. so if we were to change B to:

(defn B [end]
  [:div
    (for [n (range end]
      [A n])])
and render it with [B 10], and then re-render it with [B 9], it would need to re-render every single A child. but if we add keys to it, it will see that we’ve only removed the last element and not re-render all of the children:
(defn B [end]
  [:div
    (for [n (range end)]
      ^{:key n} [A n])])