helix

https://github.com/Lokeh/helix
Aron 2020-05-23T00:08:20.451100Z

thanks, that's very similar than to what I see

Aron 2020-05-23T00:08:45.451500Z

my bundle is 755kb total šŸ˜„

Lucy Wang 2020-05-23T03:01:23.451600Z

Makes sense. Thx.

2020-05-23T10:19:12.451800Z

any idea when you might cut a release for Helix, at all? I'm currently working around https://github.com/Lokeh/helix/commit/45c9cdf4c93ba81f178be43c6bc69ffcd3c7b505 in my $ macro and also have custom class behaviour, so both changes would be welcome to see released :)

Aron 2020-05-23T10:27:13.452400Z

yeah, I wanted to ask about "the release cycle" as well, I just didn't want to be rude : S

alidlorenzo 2020-05-23T14:47:18.453200Z

@dominicm is react* refresh working for you with the commit @lilactown linked above? iā€™m unable to get it working with defnc factory

dominicm 2020-05-23T14:47:47.453500Z

I haven't tried yet :)

šŸ†— 1
krzyz 2020-05-23T14:57:46.457800Z

Question for people in here. On state management in general, in React, and helix. I see how hooks are excellent for component state management. You have use-state for local state in something like an input, or use-reducer for more complex state that might be shared by multiple components. What about global app state? Do people favor re-frame/redux and having a nice centralized app db? Or trying to keep state where it is needed? I see good arguments for both. In the React world, Redux seems really efficient for everything other than local component state, since you give only the props that are needed. I guess people are just using all of them? Or is anyone trying to get by with just hooks and passing props where necessary?

alidlorenzo 2020-05-23T15:33:34.457900Z

there was a recent discussion about global state (https://clojurians.slack.com/archives/CRRJBCX7S/p1589820944328100) basically state management options are in a flux bc concurrent react has been unstable, so best to stick with hooks + context API if you can, since itā€™s easier to migrate out of that

krzyz 2020-05-23T15:42:24.458200Z

Thanks for sharing. I actually read that, but had the same thoughts as the poster, that context would make sense if not for the fact that consumers re-render on state changes. So it is best for simple things like an app theme, which doesn't change frequently.

krzyz 2020-05-23T15:43:49.458400Z

I agree with you also, I am leaning towards sticking to plain React as much as possible for ease of migration later, which basically means use hooks/props. Hmm, guess I will do that and re-evaluate if necessary later.

krzyz 2020-05-23T15:46:43.458600Z

Maybe if I just make sure context is close to the components that need it, then it won't be that bad. Thanks, that was helpful.

lilactown 2020-05-23T15:48:16.459300Z

Please report back if you can or cannot get it to work... I havenā€™t had time to test it thoroughly

lilactown 2020-05-23T15:48:41.459900Z

@tekacs iā€™ll do a release this in an hour or so

lilactown 2020-05-23T15:50:00.460700Z

my opinion is that ā€œglobal app stateā€ isnā€™t the right problem to think about

lilactown 2020-05-23T15:50:24.461400Z

there are many kinds of ā€œglobalā€ state that might want different solutions

lilactown 2020-05-23T15:53:02.463Z

if you break it down like: ā€¢ theme ā€¢ navigation ā€¢ data cache ā€¢ notifications ā€¢ modals each of these are cross-cutting concerns for your application, but can have very different requirements from each other

alidlorenzo 2020-05-23T15:55:42.465200Z

@krzyzowiec thereā€™s also libraries that make it easier to use react context and avoid rerenders one Iā€™ve tried out in a past js project is constate: https://github.com/diegohaz/constate

krzyz 2020-05-23T15:57:21.466600Z

@lilactown True. I like the simplicity of the idea of dealing with them the same way, but you are right that they are not the same.

alidlorenzo 2020-05-23T15:57:37.467Z

I cannot get it to work. At first I thought it was because project uses React Native Web but even with a basic counter using helix dom it does not rerender

krzyz 2020-05-23T15:57:42.467400Z

@alidcastano Ty, I will check that out

alidlorenzo 2020-05-23T15:57:48.467600Z

example counter code Iā€™m using:

(defnc App
  []
  (let [[count set-count] (h/use-state 0)]
    (d/div {:on-click #(set-count (inc count))}
           (d/button {:on-click #(set-count (inc count))} "Add")
           count)))

(defn ^:export mount
  [el]
  (rdom/render (App) (js/document.getElementById el)))

alidlorenzo 2020-05-23T15:58:07.467800Z

this works when defnc has helix factory off, but now when it has it on

lilactown 2020-05-23T15:59:02.468400Z

can you show me the macroexpand when factory is on?

lilactown 2020-05-23T15:59:11.468900Z

iā€™m on mobile atm, I can pull my computer out here in a few mins

lilactown 2020-05-23T16:00:10.470100Z

I think that if we were to put on our Rich Hickey hat, that itā€™s ā€œeasyā€ to put it all in one place, but ā€œsimpleā€ would be to manage them separately :D

šŸ’Æ 2
lilactown 2020-05-23T16:01:25.470900Z

anyway I know that itā€™s a non-answer, since I donā€™t really have a recommendation for how to manage all of those

lilactown 2020-05-23T16:02:32.472400Z

what makes sense to me right now is to put it all in context, and hide it behind a hook that I can change to use a mutable store later if perf starts to suck

krzyz 2020-05-23T16:03:14.472600Z

Right, interesting. Does that mean you would (if starting a new app) try to avoid using re-frame?

krzyz 2020-05-23T16:04:54.472800Z

I can see now, thinking about it, that context would work better if you made sure to keep concerns narrow and have few consumers. (if you assume state updates will be frequent enough to affect performance)

alidlorenzo 2020-05-23T16:05:18.473Z

hm example above just prints (App)

alidlorenzo 2020-05-23T16:05:44.473200Z

(macroexpand '(App))

lilactown 2020-05-23T16:14:57.473900Z

ah sorry, try macroexpanding the defnc

lilactown 2020-05-23T16:26:05.474100Z

(do
 (if goog/DEBUG (def sig36351 (helix.core/signature!)))
 (def
  c-render-type
  (clojure.core/->
   (clojure.core/fn
    c-render-type
    [props__33703__auto__ maybe-ref__33704__auto__]
    (clojure.core/let
     [[]
      [(helix.core/extract-cljs-props props__33703__auto__)
       maybe-ref__33704__auto__]]
     (if goog/DEBUG (clojure.core/when sig36351 (sig36351)))))
   (clojure.core/cond->
    (clojure.core/true? goog/DEBUG)
    (clojure.core/doto
     (goog.object/set "displayName" "cljs.user/c")))))
 (def c (helix.core/cljs-factory c-render-type))
 (clojure.core/when
  goog/DEBUG
  (clojure.core/when sig36351 (sig36351 c-render-type "" nil nil))
  (helix.core/register! c-render-type "cljs.user/c")) 
 c)
this looks correct..

lilactown 2020-05-23T16:40:28.475300Z

v0.0.11 is out!

šŸŽ‰ 3
alidlorenzo 2020-05-23T16:52:33.475900Z

what specifically is that testing? i.e. I see the {:updatedFamilies {,,,} :staleFamilies {,,,}} in the console but 1) if I change some text I donā€™t see it update 2) it isnā€™t showing me whether hook state is preserve, which is why I used the counter example

lilactown 2020-05-23T16:52:57.476200Z

I was testing just that editing the text updated

lilactown 2020-05-23T16:53:07.476400Z

I just tested that state works too

alidlorenzo 2020-05-23T16:53:46.476600Z

weird copied and pasted same code, might be my shadow-cljs setup :thinking_face:

lilactown 2020-05-23T16:58:24.476900Z

yeah I wouldnā€™t use re-frame

lilactown 2020-05-23T16:58:36.477100Z

you can pass an event emitter into context too

lilactown 2020-05-23T16:58:44.477300Z

similar to what redux does

lilactown 2020-05-23T16:59:14.477500Z

but I would create custom hooks for specific things, and pass in the global state via context

lilactown 2020-05-23T17:01:24.477700Z

e.g.:

(def theme-context (helix.core/create-context {,,,}))

(defnc theme-provider
 [{:keys [children]}]
 (let [[current-theme set-theme] (hooks/use-state {:mode :light})]
   ($ (.-Provider theme-context)
      {:value current-theme}
      children)))

(defhook use-theme
  []
  (let [current-theme (hooks/use-context theme-context)]
    ,,,))

lilactown 2020-05-23T17:03:18.478Z

the implementation of use-theme and theme-provider is completely hidden from your components, so you can later have it use a global atom that you pass into context instead of local state

lilactown 2020-05-23T17:04:38.478300Z

Iā€™m uploading a repo so you can look at it

šŸ‘ 1
lilactown 2020-05-23T17:11:13.478600Z

https://github.com/Lokeh/helix-fast-refresh-factory

krzyz 2020-05-23T17:20:57.479300Z

Oh that's fantastic ty! That clears up a lot for me.

alidlorenzo 2020-05-23T17:27:12.479500Z

donā€™t know why I didnā€™t try this earlier but nuked all deps in my monorepo and itā€™s working

šŸ˜± 1
alidlorenzo 2020-05-23T17:27:34.479800Z

appreciate the help

lilactown 2020-05-23T17:27:36.480Z

rough, but Iā€™m glad itā€™s working now

lilactown 2020-05-23T17:27:46.480200Z

sure thing. it forced me to actually test my code, too šŸ™‚

āœ”ļø 1
lilactown 2020-05-23T18:07:48.480800Z

I just had a really dumb idea

lilactown 2020-05-23T18:11:38.483400Z

what if you could write inside of a component:

(component-a {:foo "bar"}
 (component-b (some-fn "baz")))
and defnc walked the body of your component, looked for any lists starting with a symbol that had some metadata that it was a React component, and expanded it to:
($ component-a {:foo "bar"}
   ($ component-b (some-fn "baz")))

alidlorenzo 2020-05-23T18:14:12.484200Z

I was attempting to do something similar but then started using hicada with helix instead

lilactown 2020-05-23T18:18:06.484900Z

I already have the same concept working with https://github.com/Lokeh/helix/pull/58

alidlorenzo 2020-05-23T18:19:43.486600Z

the nice thing about your example is it works with custom components, whereas hicada only converts the primitives youā€™ve hooked up into it

alidlorenzo 2020-05-23T18:20:04.487300Z

but i find for custom components iā€™m just calling them as factory functions

lilactown 2020-05-23T18:21:12.488400Z

I think the sad path is the tricky part. if someone tries to call a component like a function, but it doesnā€™t have the correct metadata on it, then it will fail at runtime with a variety of errors because youā€™re literally invoking the component as a function

alidlorenzo 2020-05-23T18:21:47.488500Z

is this the PR you meant to link? i see the ^memo and ^callback helpers not the $ stuff

lilactown 2020-05-23T18:23:05.488700Z

yeah it is. what I meant is that the same concept that allows ^:memo and ^:callback to be expanded to (use-memo ,,,) and (use-callback ,,,) can be applied to calling components like (component ,,,) and expanding it to ($ component ,,,)

āœ”ļø 1
alidlorenzo 2020-05-23T18:31:10.489400Z

I was thinking maybe you could catch/warn for those errors for simple cases cause youā€™d know whether itā€™s a custom component or not.. but i guess you wouldnā€™t know whether it is or isnā€™t a callable function

alidlorenzo 2020-05-23T18:31:58.490200Z

as a side note, maybe the functionality shouldnā€™t be overloaded in $ but some other utility like $> so people that use it can be aware of shortcomings

lilactown 2020-05-23T18:33:55.491100Z

it wouldnā€™t be overloading $, I would just walk all the forms inside defnc and do the transformation there

lilactown 2020-05-23T18:35:39.493Z

that way you could do weird things like:

(let [el0 (my-component {:number 0})]
  (<> el0
      (for [n (range 1 10)
            :let [elN (my-component {:key n :number n})]
        elN)))

lilactown 2020-05-23T18:35:59.493500Z

and all those calls to (my-component ,,,) would get expanded to ($ my-component ,,,) inline

lilactown 2020-05-23T18:36:44.494200Z

otherwise youā€™d have to do:

(let [el0 ($> (my-component {:number 0}))]
  (<> el0
      (for [n (range 1 10)
            :let [elN ($> (my-component {:key n :number n}))]
        elN)))
which doesnā€™t seem as useful

alidlorenzo 2020-05-23T18:38:23.495200Z

oh I meant youā€™d use $ for single components but $> for multiple that youā€™d like converted into $ but i think i like your example better

Aron 2020-05-23T19:46:30.495300Z

my favorite pattern for components is to export a reducer šŸ˜„

Aron 2020-05-23T19:47:12.495500Z

and some event handler functions that can be configured with namespaces so different versions of the same component although use exactly the same code to maintain state at the same time, they write in different places

Aron 2020-05-23T19:47:42.495700Z

context is not slow for simple SPA navigation and basic form validation - interactions