cjohansen / portfolio

Eclipse Public License 1.0
236 stars 14 forks source link

Reagent-18 defscene doesn't rerender on code change #20

Open ShakoFarhad opened 8 months ago

ShakoFarhad commented 8 months ago

Reagent with react 18 has this issue where deeply nested components don't rerender on file change. To force a rerender on all parts of the component, regardless of how deeply nested it is, you need to call it as a function because then the whole component is concidered as one instead of containing react children. Example:

(defn render [] (r.dom.client/render root [:f>
                                           (if goog.DEBUG
                                             (fn [] views/main-panel) ;; Need to add this to force rerender in react 18
                                             views/main-panel)]))

Of course switching between scenes loads in a component that reflects the file changes, but this is cumbersome. Best is to force react to rerender the whole component tree everytime it is mounted when in dev mode.

Perhaps also just removing this optimization from portfolio.reagent-18/component-impl function

(if (fn? component)
  [component]
  component)

and instead just have

component

That way the user can force rerender by sending down a function component if they want to.

Or perhaps this would make sense:

(if goog.DEBUG
  [(fn [] component)]
  (if (fn? component)
    [component]
    component))

Perhaps if there is a function that can be called to force rerender, that would work great too. Not sure if this function exists already.

(defn ^:dev/after-load after-load []
  (canvas/render))

Update: After testing these changes locally, it seems like the canvas component render function is never called on code change that is deeply nested. the (ui/start! {:on-render #(println "on-render")}) is called concistently, but never (r.dom.client/render ...). So a forceful call to this whenever code changes is needed.

cjohansen commented 8 months ago

When you say "forcefull render", do you mean replacing this:

(if (fn? component)
  [component]
  component)

With just component? Sorry, I don't know a lot about how Reagent works. If that's all, then that's a trivial fix at least.

ShakoFarhad commented 8 months ago

No, I did try that change and many more things. The issue is that the the component is not getting a rerender call at all. The (ui/start! {:on-render #(println "on-render")}) function is being called every time I make a file change but the component inside the iframes render function is not being called.

I am not sure how you determine when to make a iframe rerender call, but that function needs to be exposed so that it can be manually called too when needed.

cjohansen commented 8 months ago

I have an attempt at a solution. Try using Portfolio from git:

{:git/url "https://github.com/cjohansen/portfolio.git"
 :sha "0795a711f4ab4a13ea1f08be1408b64c2310b469"}

Then, where you instantiate Portfolio, add a shadow-cljs after-load hook:

(defonce app
  (ui/start! { ,,, }))

(defn ^:dev/after-load reload []
  (ui/reload! app))
ShakoFarhad commented 8 months ago

For some reason I am not able to start portfolio. The initial documentation page loads but I am getting these errors:

  63 | (defscene
-------^------------------------------------------------------------------------
 Use of undeclared Var portfolio.reagent-18/create-scene
--------------------------------------------------------------------------------

Also this in the console:

Uncaught ReferenceError: Prism is not defined
    at Function.eval (markdown.cljs:44:23)
cjohansen commented 8 months ago

That's strange. I created a separate repo to isolate the issue, but did not see that error. Does this run for you? https://github.com/cjohansen/portfolio-reagent-rendering