Fork me on GitHub
#helix
<
2021-08-11
>
kennytilton00:08:34

So I am wondering how I broke hot reloading on my Helix+RN effort using my own code for state management. shadow-cljs does recompile when the code changes and I save. As an experiment I just re-enabled "fast refresh" and that works when I save, but of course is not optimal. FWIW, I am using a local install of the Helix master branch to take advantage of fnc, aka anonymous components. I keep the browser debugger open and am getting by fine with a manual "reload app". my shadow-cljs.edn is the same as the helix react native example. My deps.edn is :

{:paths ["src"]
 :deps  {thheller/shadow-cljs        {:mvn/version "2.8.88"}
         lilactown/helix             {:mvn/version "0.1.2-SNAPSHOT"}
         com.andrewmcveigh/cljs-time {:mvn/version "0.5.2"}
         com.taoensso/tufte          {:mvn/version "2.2.0"}
         tiltontec/matrix            {:mvn/version "4.1.1-SNAPSHOT"}}}
I suspect this is not enough to go by, but any ideas welcome. Should I be asking in #shadow-cljs instead?

kennytilton00:08:37

Ugh. Just noticed the bundler throwing an error;

Error: Unable to resolve module `./debugger-ui/debuggerWorker.d9da4ed7` from ``: 

None of these files exist:
  * debugger-ui/debuggerWorker.d9da4ed7(.native|.native.js|.js|.native.json|.json|.native.ts|.ts|.native.tsx|.tsx)
  * debugger-ui/debuggerWorker.d9da4ed7/index(.native|.native.js|.js|.native.json|.json|.native.ts|.ts|.native.tsx|.tsx)
    at ModuleResolver.resolveDependency (/Users/kennethtilton/dev/matrix/cljs/mxnative/react-native/node_modules/metro/src/node-haste/DependencyGraph/ModuleResolution.js:163:15)
Pertinent?

kennytilton01:08:12

Nope. I fixed what was breaking the bundler. Still no hot reloading.

lilactown17:08:26

I'm not super familiar with using fast refresh with RN

lilactown17:08:49

one thing to note is that helix's support for react's hot reloading capabilities is done via defnc

lilactown18:08:40

we need to register certain information with react, like the name of the component and what hooks are used inside of it, so that react knows whether to re-mount any instances of the component or not

lilactown18:08:37

I know that you're mostly using fnc, right? which doesn't do any of that registration. react will deopt to re-mount any components it doesn't have any information about

lilactown18:08:16

now, if all of your state is being stored outside of the react component tree then you could get by using defonce on your reactive values, like people do with reagent/re-frame

kennytilton19:08:51

Thx, @U4YGF4NGM. Interesting. I did not realized how targeted was hot reloading. Hitting the refresh button is not the end of the world, and it sounds like, where I decide to go with a named defnc component, I will get some hot reloading. I have every component allocate a useState hook. When matrix state management sees a change to a component, it calls setState passing the internal state "clock" that drives matrix state management. Application state is spread across a tree of atoms, each bound to some property of some map. React has no idea what I am up to, it just sees all these setState calls of a monotonically increasing value. So far so good. btw, I programmed this way for twenty years without a problem with hot reloading. Hot reloading is nice, but in fact is just the fix for eternal build times occasioned by nonsense tool chains try to get something programmable out of crap like CSS and JS. We shoulda used Lisp for the Web. Natively, I mean. Is no one working on a WebASM Lisp? :)

lilactown19:08:27

yeah, the issue is that most JS bundlers (for various good and bad reasons) build your code as a bunch of nested IIFEs

lilactown19:08:36

which breaks the lisp-like ability to reload specific functions or data, because any references to them have been closed over by their dependents and are stale

lilactown19:08:42

this is different than how CLJS compiles, because we have namespaces. so we can do things like redef a function via the REPL and depends will pick it up under most circumstances

lilactown19:08:04

or we can recompile and reload a namespace but trivially use something like defonce without doing any hacking around closures

lilactown19:08:46

it sounds like if all state is stored outside of the tree, then you should be able to persist that state using something like defonce

kennytilton07:08:17

Haha, that confirms how little I understand about the coolio things going on behind the scenes. But thanks for the glimpse. I have to look up IIFEs next. With all state in one atom, does not React think all view functions need to run when that one atom changes? In which case only the internal React VDOM diff heads off unnecessary DOM manipulation? Or do things like Reagent/re-frame use shouldComponentUpdate to avoid regenerating VDOM where not needed?

lilactown14:08:43

reagent/re-frame do things similar to what it sounds like matrix does, which is anytime an atom changes it calls setState in the components listening to it. if your components are all listening to one atom, then yeah they will all re-render. you can create multiple atoms for different state. reagent & re-frame also give you tools to create dependent calculations which will cache their values, so that they only re-render components listening to them when the value is different. a typical pattern for a reagent or re-frame app will be to store all state in one atom that is kept in some lone namespace in a defonce, and then create dependent calculations which are used throughout your components. then when your components or calculations change, you can just restart the app, but the state in the atom persists

lilactown14:08:32

IIFE stands for "immediately invoked function expression" and is javascript-web-lingo for a closure. i.e. ((fn [a] (prn a)) "foo")

kennytilton12:08:57

Heh-heh, thx @U4YGF4NGM. I did look it up and was all, "Oh, that." 🙂 I did get into Reagent pretty well recreating my WhosHiring browser in pure Reagent. Felt very much like Matrix. Matrix spreading state across dynamically allocated atoms has the win of hiding state management altogether, but I fear will break hot reloading. Gonna be tough weaning the kids off that. 🙂

jjttjj17:08:48

I'm sort of new to react and I've been somewhat slow to fully comprehend "the react way" via helix. What's the best way to compose layouts and content with helix? Like if I want a layout component that has a main area and a sidebar, and I want that as a component where I plug in other components in each of those areas. Is this where the "custom macro" pro tip comes in? Or do I need to look into higher order components more

tvaughan17:08:11

I don't know. But one thing I've seen happen a lot, when users must be authenticated, are "shells" like this where authentication is an after thought which results in a sign-in component in the main area with the sidebar and top-menu rendered around it. The sign-in component should be a top-level component too. Planning ahead for a situation like this in the early stages could be helpful.

lilactown17:08:40

I don't think macros or higher order components would be necessary

lilactown17:08:46

typically we have layout components that take children, i.e.:

($ page
   ($ sidebar
      (d/ul
        (for [item menu-items]
          (d/li (d/a {:href (:href item)} (:label item)))))
   ($ main
      (d/h1 "Welcome to the app!")
      (d/p "We hope you have a nice time")))

lilactown17:08:36

page, sidebar and main are components that define some structure and styling and render any children passed to them

(defnc page
  [{:keys [children]}]
  (d/div {:class "page"} children))

(defnc sidebar
  [{:keys [children]}]
  (d/div {:class "sidebar"} children))

(defnc main
  [{:keys [children]}]
  (d/div {:class "main"} children))
obviously you could have more complex structures in there. you could also define any styling you need inline, but CSS classes are typically better for large apps

jjttjj18:08:18

Gotcha, thanks! I think I wasn't thinking about the children prop and that gets me some (maybe all?) of the way there definitely. I also found this https://github.com/keechma/keechma-next-toolbox/blob/75f7c44cba3c5e827b0340479a57c42aa63c3982/src/keechma/next/helix/template.clj, which I think might be what I'm looking for but haven't quite got it working yet

lilactown18:08:02

that looks quite a bit more complicated

lilactown18:08:48

you can also pass in named props, if you want to have more control over the layout. i.e.

($ page
   {:sidebar (d/ul
              (for [item menu-items]
                (d/li ,,,)))
    :main (<>
            (d/h1 "Welcome to the app!")
            (d/p "We hope you have a nice time"))})

jjttjj18:08:20

ah! I think might be just what I needed

lilactown18:08:56

and then the page component can ensure they get put in the right order:

(defnc page
  [{:keys [sidebar main]}]
  (d/div
   {:class "page"}
   (d/div {:class "sidebar"} sidebar)
   (d/div {:class "main"} main)))

jjttjj18:08:51

Yup that's just what I need thanks!

2
wilkerlucio18:08:19

another thing to consider is to just not have components for layout, since they are just composition of components, in this way, when using something like Tailwind CSS, you can just make layout arrangements as you need

lilactown18:08:18

I use tailwind, but tend to extract common combinations of tailwind classes into React components, i.e.:

(defnc page
  [{:keys [children]}]
  (d/div {:class "container mx-auto py-2"} children))
I know that tailwind has its own ability to define "component classes" using postcss(?) or whatever css toolchain you're using with it, but I haven't used that yet.

wilkerlucio18:08:10

for those cases I have a custom macro that I like to use, that just wraps a tag with some initial classes that may be modified when instantiating them