Fork me on GitHub
#helix
<
2020-04-30
>
fabrao17:04:44

Hello all, I have one question about when to use #js in props using with ($ Compoment {...props...} children)?

lilactown18:04:01

if Component expects a JS object or array as a prop, then you will need to pass it one

fabrao18:04:49

@lilactown so, all the props level 1 and nested?

lilactown18:04:26

helix does some special handling for the :style prop of elements like div / span / etc.

lilactown18:04:34

otherwise, yes level 1 and nested

lilactown18:04:54

($ Component {:some-js-value #js {,,,}} ,,,)

fabrao18:04:43

Like here

(:require ["bizcharts" :refer (Chart Line Point)])

(defnc Graphic []       
	($ Chart
          {:autoFit true
           :height 500
           :data (into-array [{:year "1990" :value 3}
                              {:year "1991" :value 6}
                              {:year "1992" :value 10}])
           :scale {:value {:min 0}}}
          ($ Line {:position "year*value"}))

fabrao18:04:15

wich one do I have to use it?

lilactown18:04:35

I don’t know, I’ve never used bizcharts

fabrao18:04:00

but is basicaly is React Component

fabrao18:04:27

is not all JS objects?

lilactown18:04:58

helix will rewrite

{:autoFit true
 :height 500
 :data (into-array ,,,)
 :scale {:value {:min 0}}}
into
#js {:autoFit true
     :height 500
     :data (into-array ,,,)
     :scale {:value {:min 0}}}

lilactown18:04:15

so the top-level props will be a JS object, but any prop values are left alone

fabrao18:04:32

but the nested one?

lilactown18:04:40

it doesn’t not handle nesting!

lilactown18:04:49

if it expects the value of scale to be a nested JS obj, you’ll need to make sure you pass in a JS obj

fabrao18:04:52

like here (my-component #js {:person #js {:firstName "Miguel" :lastName "Ribeiro"}}) in docs

lilactown18:04:32

if it expects data to be an array of objects, you will need to make sure you pass in an array of objects - not a vector of maps

lilactown18:04:36

helix will not rewrite it for you

fabrao18:04:40

sorry, I´m little confuse yet

lilactown18:04:43

{:autoFit true
 :height 500
 :data #js [#js {,,,} ,,,]
 :scale #js {:value #js {:min 0}}}

lilactown18:04:01

helix doesn’t convert any values of props

lilactown18:04:42

does that make sense?

fabrao18:04:45

why is it not convert it?

lilactown18:04:59

because you might not want to

lilactown18:04:14

if your component takes CLJS vectors and maps, you wouldn’t want those to become arrays and objects

fabrao18:04:43

well, do you think is it a good thing to diff own component from JS component?

Aron18:04:03

they are not different\

Aron18:04:34

what is different is the data that they use, js components need js data, cljs components need cljs data

lilactown18:04:44

also many times JS components can take CLJS vectors and arrays too

Aron18:04:50

if you write component that doesn't care about what data it gets, you can use it both places

lilactown18:04:42

e.g. react-select can be given an array of CLJS maps and a custom :getOptionValue :getOptionLabel prop

lilactown18:04:11

which is much better than converting all of your data to JS objects, and then converting all back to maps when you get it back from react-select

Aron18:04:23

well, why would anyone use react-select though 😄

lilactown18:04:39

there’s too many different cases. you are the best person to know what kind of value to pass to your component, not helix

fabrao18:04:59

ok, understood

Aron18:04:03

conversion is very costly

Aron18:04:11

but bean ->js is good

Aron18:04:04

what I found often is that I think I need bean, I use it, and then it turns out there is a simpler way which doesn't require conversion

fabrao18:04:26

yes, but you usualy use JS components that you will need to take care about

Aron18:04:28

so as an intermediary hack is very very often used for now, I expect if I get more experience I will use it less

Aron18:04:57

I actually use material-ui right now, that's react components, it just works

fabrao18:04:47

I always have to test with #js and without. What should I take care about it?

lilactown18:04:14

if you’re reading docs for an external React component and it has you pass in objects/arrays, you probably have to pass in objects and arrays

aiba18:04:35

Is there a way to get (helix.hooks/use-effect [foo] ...) to use cljs.core/= for checking whether foo changed? I'd like to use an effect that depends on changes to a non-primitive clojure value.

lilactown18:04:30

you can use another hook to maintain referential identity if foo is deep equals but for some reason a different reference

lilactown18:04:59

but there are only a few cases where that should be necessary; why can’t you rely on foo being the same reference to check for equality?

aiba18:04:59

in my case, foo ends up always being the result of another function, which might might get re-run to produce a cljs.core/= value

aiba18:04:53

i.e., (let [foo (some-function-of-current-state)] (use-effect [foo] ...))

lilactown18:04:04

you should probably memoize that function call

aiba18:04:39

that would work! i'd have to be careful tho to turn it into a pure function, as it currently dereferences state inside its body

lilactown18:04:21

yes, your render function should be pure

lilactown18:04:34

otherwise you’re going to have bugs anyway

aiba18:04:55

ok, put another way, the function is inside the scope of the defnc and uses state variables that are in the lexical scope of its body

aiba18:04:18

which i think is generally ok to do

lilactown18:04:33

then you should be able to memoize it just fine with use-memo

aiba18:04:19

oh, use-memo! i was thinking cljs.core/memoize

aiba19:04:08

but then i think i'm back to the same problem where i have to explicitly declare the deps to the memoized function body (and those deps won't be compared by value if they change), am i missing something?

lilactown19:04:58

yes that’s correct

aiba19:04:52

sorry to belabor this, maybe i'm missing some concept. i think what i really want to express is "run this effect whenever the value of foo changes". i like the idea of using a separate effect to track the value of the deps using the value semantics of my choice. maybe i'll just wrap that up into a higher-order use-effect that takes in an extra arg about how values are compared

aiba19:04:55

thanks for that idea!

lilactown19:04:36

the idea is that you should try and ensure that your reference changes only if your value changes

lilactown19:04:53

sometimes this is unavoidable, but often it is not

aiba19:04:07

and here when you say "reference" you mean like js pointer, not react/useRef (just to clarify)

lilactown19:04:47

yeah, things like:

(let [some-static-data {:foo "bar"} ;; bad, recreated every render
      some-calculation (use-memo
                        [some-static-data]
                        (assoc :foo "baz"))]
(def some-static-data {:foo "bar"}) ;; good - only created once, keeps same reference identity

(let [some-calculation (use-memo
                        [some-static-data]
                        (assoc :foo "baz"))]

lilactown19:04:40

you want to write your code so that if two values are = then they are probably identical? too

lilactown19:04:15

using things like use-memo and use-callback to avoid re-processing or re-creating the same stuff every render helps a lot here

aiba19:04:03

i see, yeah that seems like the way to get the best performance

aiba19:04:23

i'm really just not used to reasoning about whether two values being = implies they are identical?

aiba19:04:37

like in larger components, i could see that being quite hard to reason about

lilactown19:04:06

almost always things are identical? when they’re = if you structure things right

👍 4
lilactown19:04:37

that’s the the main benefit of immutable data

lilactown19:04:25

the strategy is: • build the initial value once • calculate only when things change if you follow those two rules, then you’ll be golden

lilactown19:04:54

there are some cases like e.g. deserializing data from an external source (e.g. localStorage, or an HTTP endpoint), where you can’t tell if the data will be the same until you do a deep comparison like =

lilactown19:04:53

in those specific cases, you can use a custom hook to check to see if the two values fails = and if so, return the new value, otherwise, keep returning the old one and toss the new one away

aiba19:04:54

i see, yeah agree that seems ideal way to get best performance, and i wish i had been building my app with that mindset. alas, i've been cavalier about this stuff and not worried at all about performance so far, and this could be quite a bit of restructuring, but i will think more on it. thank you for these insights!

aiba19:04:53

this discussion could actually have a valuable place in the helix docs, i could try to draft a summary if you'd like. i've found one of the tricky things in general about interop'ing with react from clojure is to understand which kind of value semantics are being applied when

lilactown19:04:34

helix matches whatever React does, which is always comparing things by reference

lilactown19:04:04

I might put something in the pro-tips or FAQ about it, but I’m really repeating what are React best practices

aiba19:04:33

OK, makes sense. Yeah fwiw i think it would be helpful to make it clear that helix is always comparing things by identical? I like that that's easy to remember 🙂