helix

https://github.com/Lokeh/helix
fabrao 2020-04-30T17:49:44.042400Z

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

lilactown 2020-04-30T18:15:01.044400Z

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

fabrao 2020-04-30T18:18:49.045400Z

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

lilactown 2020-04-30T18:19:26.045900Z

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

lilactown 2020-04-30T18:19:34.046200Z

otherwise, yes level 1 and nested

lilactown 2020-04-30T18:19:54.046700Z

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

fabrao 2020-04-30T18:21:43.047100Z

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

fabrao 2020-04-30T18:22:15.047400Z

wich one do I have to use it?

lilactown 2020-04-30T18:22:35.047700Z

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

fabrao 2020-04-30T18:23:00.048400Z

but is basicaly is React Component

fabrao 2020-04-30T18:23:27.049100Z

is not all JS objects?

lilactown 2020-04-30T18:23:58.049800Z

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

lilactown 2020-04-30T18:24:15.050200Z

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

fabrao 2020-04-30T18:24:32.050900Z

but the nested one?

lilactown 2020-04-30T18:24:40.051200Z

it doesn’t not handle nesting!

lilactown 2020-04-30T18:24:49.051600Z

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

fabrao 2020-04-30T18:24:52.051700Z

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

lilactown 2020-04-30T18:25:32.052500Z

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

lilactown 2020-04-30T18:25:36.052700Z

helix will not rewrite it for you

fabrao 2020-04-30T18:26:40.053300Z

sorry, I´m little confuse yet

lilactown 2020-04-30T18:26:43.053400Z

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

fabrao 2020-04-30T18:26:59.053800Z

ok

lilactown 2020-04-30T18:27:01.053900Z

helix doesn’t convert any values of props

lilactown 2020-04-30T18:27:42.054500Z

does that make sense?

fabrao 2020-04-30T18:27:45.054700Z

why is it not convert it?

lilactown 2020-04-30T18:27:59.055Z

because you might not want to

lilactown 2020-04-30T18:28:14.055400Z

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

fabrao 2020-04-30T18:29:43.057Z

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

Aron 2020-04-30T18:30:03.057300Z

they are not different\

Aron 2020-04-30T18:30:34.058Z

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

lilactown 2020-04-30T18:30:44.058500Z

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

Aron 2020-04-30T18:30:50.058800Z

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

lilactown 2020-04-30T18:31:42.059500Z

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

lilactown 2020-04-30T18:32:11.060400Z

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

Aron 2020-04-30T18:32:23.060900Z

well, why would anyone use react-select though 😄

lilactown 2020-04-30T18:32:39.061400Z

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

fabrao 2020-04-30T18:32:59.061700Z

ok, understood

Aron 2020-04-30T18:33:03.061800Z

conversion is very costly

Aron 2020-04-30T18:33:11.062100Z

but bean ->js is good

Aron 2020-04-30T18:33:21.062300Z

https://github.com/mfikes/cljs-bean

Aron 2020-04-30T18:34:04.063500Z

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

fabrao 2020-04-30T18:34:26.064300Z

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

Aron 2020-04-30T18:34:28.064400Z

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

Aron 2020-04-30T18:34:57.064900Z

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

fabrao 2020-04-30T18:35:47.065800Z

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

lilactown 2020-04-30T18:37:14.066400Z

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

aiba 2020-04-30T18:44:35.068400Z

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.

lilactown 2020-04-30T18:48:30.069300Z

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

lilactown 2020-04-30T18:48:59.069900Z

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?

aiba 2020-04-30T18:49:59.070700Z

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

aiba 2020-04-30T18:50:53.071300Z

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

lilactown 2020-04-30T18:52:04.072200Z

you should probably memoize that function call

aiba 2020-04-30T18:53:39.073100Z

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

lilactown 2020-04-30T18:55:21.074Z

yes, your render function should be pure

lilactown 2020-04-30T18:55:34.074300Z

otherwise you’re going to have bugs anyway

aiba 2020-04-30T18:56:55.074900Z

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

aiba 2020-04-30T18:57:18.075200Z

which i think is generally ok to do

lilactown 2020-04-30T18:58:33.077Z

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

aiba 2020-04-30T18:59:19.077400Z

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

aiba 2020-04-30T19:02:08.078900Z

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?

lilactown 2020-04-30T19:02:58.079800Z

yes that’s correct

aiba 2020-04-30T19:03:52.080800Z

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

aiba 2020-04-30T19:03:55.081Z

thanks for that idea!

lilactown 2020-04-30T19:04:36.081600Z

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

lilactown 2020-04-30T19:04:53.081900Z

sometimes this is unavoidable, but often it is not

aiba 2020-04-30T19:06:07.083800Z

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

lilactown 2020-04-30T19:06:47.084500Z

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"))]

lilactown 2020-04-30T19:07:40.085200Z

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

lilactown 2020-04-30T19:08:15.085900Z

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

aiba 2020-04-30T19:09:03.087100Z

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

aiba 2020-04-30T19:09:23.087600Z

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

aiba 2020-04-30T19:09:37.088100Z

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

lilactown 2020-04-30T19:10:06.088700Z

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

👍 1
lilactown 2020-04-30T19:10:37.089300Z

that’s the the main benefit of immutable data

lilactown 2020-04-30T19:11:25.089900Z

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

lilactown 2020-04-30T19:11:54.090400Z

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 =

lilactown 2020-04-30T19:12:53.091600Z

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

aiba 2020-04-30T19:15:54.093200Z

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!

aiba 2020-04-30T19:17:53.094900Z

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

lilactown 2020-04-30T19:22:34.095400Z

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

lilactown 2020-04-30T19:23:04.095900Z

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

aiba 2020-04-30T19:24:33.096800Z

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 🙂