Hello all, I have one question about when to use #js
in props using with ($ Compoment {...props...} children)
?
if Component
expects a JS object or array as a prop, then you will need to pass it one
@lilactown so, all the props level 1 and nested?
helix does some special handling for the :style
prop of elements like div
/ span
/ etc.
otherwise, yes level 1 and nested
($ Component {:some-js-value #js {,,,}} ,,,)
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"}))
wich one do I have to use it?
I don’t know, I’ve never used bizcharts
but is basicaly is React Component
is not all JS objects?
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}}}
so the top-level props will be a JS object, but any prop values are left alone
but the nested one?
it doesn’t not handle nesting!
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
like here (my-component #js {:person #js {:firstName "Miguel" :lastName "Ribeiro"}})
in docs
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
helix will not rewrite it for you
sorry, I´m little confuse yet
{:autoFit true
:height 500
:data #js [#js {,,,} ,,,]
:scale #js {:value #js {:min 0}}}
ok
helix doesn’t convert any values of props
does that make sense?
why is it not convert it?
because you might not want to
if your component takes CLJS vectors and maps, you wouldn’t want those to become arrays and objects
well, do you think is it a good thing to diff own component from JS component?
they are not different\
what is different is the data that they use, js components need js data, cljs components need cljs data
also many times JS components can take CLJS vectors and arrays too
if you write component that doesn't care about what data it gets, you can use it both places
e.g. react-select can be given an array of CLJS maps and a custom :getOptionValue
:getOptionLabel
prop
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
well, why would anyone use react-select though 😄
there’s too many different cases. you are the best person to know what kind of value to pass to your component, not helix
ok, understood
conversion is very costly
but bean ->js is good
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
yes, but you usualy use JS components that you will need to take care about
so as an intermediary hack is very very often used for now, I expect if I get more experience I will use it less
I actually use material-ui right now, that's react components, it just works
I always have to test with #js
and without. What should I take care about it?
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
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.
you can use another hook to maintain referential identity if foo
is deep equals but for some reason a different reference
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?
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
i.e., (let [foo (some-function-of-current-state)] (use-effect [foo] ...))
you should probably memoize that function call
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
yes, your render function should be pure
otherwise you’re going to have bugs anyway
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
which i think is generally ok to do
then you should be able to memoize it just fine with use-memo
oh, use-memo! i was thinking cljs.core/memoize
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?
yes that’s correct
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
thanks for that idea!
the idea is that you should try and ensure that your reference changes only if your value changes
sometimes this is unavoidable, but often it is not
and here when you say "reference" you mean like js pointer, not react/useRef (just to clarify)
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"))]
you want to write your code so that if two values are =
then they are probably identical?
too
using things like use-memo
and use-callback
to avoid re-processing or re-creating the same stuff every render helps a lot here
i see, yeah that seems like the way to get the best performance
i'm really just not used to reasoning about whether two values being = implies they are identical?
like in larger components, i could see that being quite hard to reason about
almost always things are identical?
when they’re =
if you structure things right
that’s the the main benefit of immutable data
the strategy is: • build the initial value once • calculate only when things change if you follow those two rules, then you’ll be golden
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 =
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
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!
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
helix matches whatever React does, which is always comparing things by reference
I might put something in the pro-tips or FAQ about it, but I’m really repeating what are React best practices
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 🙂