GithubHelp home page GithubHelp logo

Comments (8)

lilactown avatar lilactown commented on July 23, 2024 1

React components must be either a literal JavaScript function, or an object that inherits from the React.Component class. Unfortunately, multi methods do not fall into those categories.

I would recommend structuring your code in a way that the business logic is handled by multi method, but the component itself is a function. Similar to your examples:

(defmulti animals :animal)

(defmethod animals :dog
  []
  "rendering dog")

(defmethod animals :sheep
  []
  "rendering sheep")

(helix/defnc using-multimethod
  []
  (helix/$ :div (animals {:animal :dog})))

While less powerful than using the multi method as a component itself, this protects you from a potential footgun: if your multicomponent used hooks, and the :animal passed in changed, it could render new hooks or need to re-initialize them based on the method used and React wouldn't know how to do that, because the identity of methods are the same no matter the branch it takes. It would be akin to:

(defnc my-multi-component
  [{:keys [animal]}]
  (case animal
    :dog (let [[barked? set-barked] (hooks/use-state false)]
            ,,,)
    :sheep ,,,))

This is breaking the rule where hooks should never be conditionally used inside of a component.

from helix.

lilactown avatar lilactown commented on July 23, 2024 1

I appreciate the good discussion @simonacca :)

from helix.

simonacca avatar simonacca commented on July 23, 2024

Wow, thank you, I didn't consider the hook problem at all!!

I still belive it would be great to get multimethods working in this particular case as I'm using them to implement lazy-loaded plugins (so both the business logic and the components are loaded just in time when the application calls for a particular plugin).

As a last attempt, what are your thoughts on using the multimethod's dispatch-val (that is, :dog, :sheep and the like) as a React key to "force" React to realize that they are indeed different components?

Proof of concept:

(defmulti animal
  (fn [props] (.-animap props)))

(helix/defnc multimethod-wrapper [{:keys [mm] :as props}]
  (helix/$ (fn [inner-props] (mm inner-props))
          {:key ((dispatch-fn mm) (helix.impl.props/-props props))
           :& props}))

(helix/defnc using-multimethod
  []
  (helix/$ multimethod-wrapper {:mm animal :animal :dog}))

p.s. I suppose the anonymous function in multimethod-wrapper should be memoized...

from helix.

lilactown avatar lilactown commented on July 23, 2024

How exactly do you implement lazy-loading this way? I'm interested to learn.

from helix.

simonacca avatar simonacca commented on July 23, 2024

Using shadow-cljs's built-in loader. In practice:

  1. Define multimethod in one namespace
  2. Define plugins one per namespace, as methods of the above multimethod
  3. Define each plugin as a separate module in the config
  4. At runtime, make sure to load each module using shadow.loader before it is rendered

from helix.

lilactown avatar lilactown commented on July 23, 2024

Have you looked at React.lazy and [shadow.lazy], as described in thheller's blog post on code splitting?

With a bit of macro magic you can lazily load individual components really easily.

;; a lazy-loaded about page
(def about (lazy town.lilac.app.about/page)

(defnc routes
  []
  (case (use-route)
    :home ($ home)
    :about ($ about)))

(defnc app
  []
  (d/div
    ($ navigation)
    (helix/suspense
     {:fallback ($ spinner)}
     ($ routes))))

This way, you can render a fallback spinner if the module hasn't been loaded yet and is taking awhile. I'm not sure how you would do this with the multi method approach.

from helix.

simonacca avatar simonacca commented on July 23, 2024

Very interesting, didn't know about either shadow.lazy nor React.lazy, thank you!

Youst to continue the theoretical exercise, if one still wanted to use multimethods, they could for example:

  • implement the spinner as the :default method
  • tweak multimethod-wrapper to perform the lazy loading in an effect and trigger a rerender when loading has occurred

In any case, it seems clear (in my view at least) that there is nothing to change in helix per se stemming from this conversation, therefore am closing the issue. Should you have a different opinion, please feel more than welcome to reopen.

Thanks again!
Simon

from helix.

simonacca avatar simonacca commented on July 23, 2024

Likewise!

from helix.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.