Fork me on GitHub
#helix
<
2020-05-23
>
Aron00:05:20

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

Aron00:05:45

my bundle is 755kb total šŸ˜„

tekacs10:05:12

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 :)

Aron10:05:13

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

Aleed14:05:18

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

lilactown15:05:16

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

Aleed15:05:37

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

Aleed15:05:48

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

Aleed15:05:07

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

lilactown15:05:02

can you show me the macroexpand when factory is on?

lilactown15:05:11

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

Aleed16:05:18

hm example above just prints (App)

Aleed16:05:44

(macroexpand '(App))

lilactown16:05:57

ah sorry, try macroexpanding the defnc

lilactown16:05:05

(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..

lilactown16:05:14

@alidcastano @dominicm my simple test of :define-factory and :react-refresh works:

(ns app.core
  "This namespace contains your application and is the entrypoint for 'yarn start'."
  (:require
    [helix.core :refer [defnc $]]
    [helix.experimental.refresh :as r]
    ["react-dom" :as rdom]))

(defnc test-component
  [props]
  {:helix/features {:define-factory true
                    :fast-refresh true}}
  (prn props)
  ($ "p" "some text"))

(def state (atom {:some "value"}))

(defn ^:dev/after-load refresh
  "Render the toplevel component for this app."
  []
  (r/refresh!))


(defn ^:export main
  "Run application startup logic."
  []
  (r/inject-hook!)
  (rdom/render
   (test-component
    {:foo "bar"})
   (.getElementById js/document "app")))

Aleed16:05:33

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

lilactown16:05:57

I was testing just that editing the text updated

lilactown16:05:07

I just tested that state works too

Aleed16:05:46

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

lilactown17:05:38

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

šŸ‘ 4
Aleed17:05:12

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

šŸ˜± 4
Aleed17:05:34

appreciate the help

lilactown17:05:36

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

lilactown17:05:46

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

āœ”ļø 4
dominicm14:05:47

I haven't tried yet :)

šŸ†— 4
krzyz14:05:46

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?

Aleed15:05:34

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

krzyz15:05:24

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.

krzyz15:05:49

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.

krzyz15:05:43

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.

lilactown15:05:00

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

lilactown15:05:24

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

lilactown15:05:02

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

Aleed15:05:42

@U013CA8A28J 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

krzyz15:05:21

@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.

krzyz15:05:42

@alidcastano Ty, I will check that out

lilactown16:05:10

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

šŸ’Æ 8
lilactown16:05:25

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

lilactown16:05:32

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

krzyz16:05:14

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

krzyz16:05:54

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)

lilactown16:05:24

yeah I wouldnā€™t use re-frame

lilactown16:05:36

you can pass an event emitter into context too

lilactown16:05:44

similar to what redux does

lilactown16:05:14

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

lilactown17:05:24

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

lilactown17:05:18

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

krzyz17:05:57

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

Aron19:05:30

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

Aron19:05:12

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

Aron19:05:42

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

lilactown15:05:41

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

lilactown16:05:14

@alidcastano @dominicm my simple test of :define-factory and :react-refresh works:

(ns app.core
  "This namespace contains your application and is the entrypoint for 'yarn start'."
  (:require
    [helix.core :refer [defnc $]]
    [helix.experimental.refresh :as r]
    ["react-dom" :as rdom]))

(defnc test-component
  [props]
  {:helix/features {:define-factory true
                    :fast-refresh true}}
  (prn props)
  ($ "p" "some text"))

(def state (atom {:some "value"}))

(defn ^:dev/after-load refresh
  "Render the toplevel component for this app."
  []
  (r/refresh!))


(defn ^:export main
  "Run application startup logic."
  []
  (r/inject-hook!)
  (rdom/render
   (test-component
    {:foo "bar"})
   (.getElementById js/document "app")))

lilactown16:05:28

v0.0.11 is out!

šŸŽ‰ 12
lilactown18:05:48

I just had a really dumb idea

lilactown18:05:38

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

Aleed18:05:12

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

lilactown18:05:06

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

Aleed18:05:47

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

lilactown18:05:05

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 ,,,)

āœ”ļø 4
Aleed18:05:43

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

Aleed18:05:04

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

lilactown18:05:12

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

Aleed18:05:10

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

Aleed18:05:58

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

lilactown18:05:55

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

lilactown18:05:39

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

lilactown18:05:59

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

lilactown18:05:44

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

Aleed18:05:23

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