This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-04-06
Channels
- # announcements (18)
- # asami (14)
- # aws (5)
- # babashka (58)
- # beginners (32)
- # calva (12)
- # cider (29)
- # clj-kondo (8)
- # cljfx (8)
- # cljs-dev (4)
- # clojure (101)
- # clojure-europe (54)
- # clojure-germany (5)
- # clojure-nl (8)
- # clojure-serbia (8)
- # clojure-spec (12)
- # clojure-uk (8)
- # clojurescript (24)
- # cursive (3)
- # datomic (17)
- # docs (2)
- # etaoin (12)
- # events (1)
- # fulcro (1)
- # google-cloud (2)
- # jobs (1)
- # jobs-discuss (6)
- # lsp (65)
- # luminus (2)
- # malli (10)
- # meander (4)
- # nrepl (1)
- # off-topic (112)
- # onyx (2)
- # pathom (6)
- # polylith (14)
- # re-frame (9)
- # reagent (36)
- # reitit (13)
- # releases (2)
- # remote-jobs (5)
- # rewrite-clj (12)
- # shadow-cljs (70)
- # specter (2)
- # startup-in-a-month (1)
- # xtdb (14)
So I'm using error boundaries together with hot code reloading when developing with Reagent, and I've noticed that 'triggered' error boundary often don't get refreshed when my code gets reloaded. Is there a way to force them to get re-rendered on reloads?
FWIW I've tried sprinkling calls to force-update-all
in my 'on-reload' fn, but to no avail:
(reagent.dom/force-update-all)
(reagent.dom/render [views/app-root] (.getElementById js/document "app"))
(reagent.dom/force-update-all)
(defn <error-boundary>
[& _]
(let [*info (r/atom nil)]
(r/create-class
{:constructor (fn [this _props]
(set! (.-state this) #js {:error nil}))
:component-did-catch (fn [_this _e info] (reset! *info info))
:display-name "error-boundary"
:get-derived-state-from-error (fn [error] #js {:error error})
:render (fn [this]
(r/as-element
(if-let [error (.. this -state -error)]
[<error-message>
error
(some-> @*info .-componentStack)]
(into [:<>] (r/children this)))))})))
I don't see anything necessarily wrong with it, but try this one. It works perfectly in my case.
(defn component []
(let [error (reagent/atom nil)]
(reagent/create-class
{:component-did-catch
(fn [_this _e _info])
:get-derived-state-from-error
(fn [e]
(reset! error e)
#js {})
:reagent-render
(fn []
(if @error
[error-component @error]
[main-component]))})))
will do
yeah, no go 😞
I'm using shadow-cljs btw... although I don't think this should affect this sort of thing?
I have a fn that looks like
(defn ^:dev/after-load re-init
"(Re)initializes the state. Called on page load and code reload."
[]
(log/debug "Re-initing...")
(effect-handlers/init!)
(routes/init!)
(sub-handlers/init!)
(reagent.dom/force-update-all)
(reagent.dom/render [views/app-root] (.getElementById js/document "app"))
(reagent.dom/force-update-all)
(css/init-static-classes!)
(set-config!))
wait a second
I think I see something weird in my shadow-cljs config
yeah okay fixing that thing didn't help
my shadow-cljs config is basically
{...
:build {:app {...
:modules {:base {:init-fn site.core/init
:entries [site.core]}}
:devtools {:after-load site.core/re-init
:loader-mode :script
:preloads [shadow.remote.runtime.cljs.browser]}}}
I just confirmed that init
and re-init
get called on page load, and re-init
gets called on reload
weirdly I have no problems with any other elements not getting reloaded
yeah very strange
I'm gonna take another look at my init code to see if there's anything weird
perhaps I'm getting my re-frame effects mixed up
Now that you mention re-frame
- do you really store *info
in a ratom or was it just to replace some subscription?
I store it in an atom 🙂
I use plain reagent for 'intra-component' state
and yeah as it turns out, that's the problem
reagent doesn't reset *info
when I reload
I had the same problem with the error-boundary component you proposed, because it also keeps state in an atom
I'm honestly kind of stumped as to how I should approach this
basically the call order seems to be
1. get-derived-state-from-error
2. render
3. component-did-catch
I want to use the info
arg, that's only provided to component-did-catch
to trigger a re-render (so I can display that state)
is there a way to do this without wrapping the component in (let [*info (r/atom nil)] )
?
alternatively, is there a way to force that atom to be cleared when the page gets reloaded?
....okay, I finally got it working the way I want
(defn <error-boundary>
"Util for inserting an error boundaries in the React tree. When a child throws
an error, just renders that error instead of crashing."
[& _]
(let [*error (atom nil)]
(r/create-class
{:component-did-catch (fn [this e i]
(reset! *error [e (.-componentStack i)])
(.forceUpdate this))
:display-name "error-boundary"
:get-derived-state-from-error #(reset! *error [%])
:render #(r/as-element
(if-let [[error stack] @*error]
[<error-message> error stack]
(into [:<>] (r/children %))))})))
plain old non-reagent atoms (and forceUpdate
) to the rescue 🙂