Fork me on GitHub
#reagent
<
2021-12-28
>
Franklin08:12:28

I'm having some trouble with error boundaries

Franklin08:12:46

here's what the error boundary code looks like

(defn error-boundary [comp]
  (js/console.log comp)
  (let [error (r/atom nil)]
    (r/create-class
     {:component-did-catch (fn [this e info]
                             (js/console.log "sending error to sentry")
                             (js/console.log this)
                             (js/console.log e)
                             (js/console.log info))
      :get-derived-state-from-error (fn [e]
                                      (reset! error e)
                                      #js {})
      :reagent-render (fn [comp]
                        (js/console.log "does this render?" error)
                        (if @error
                          [:div
                           "Something went wrong."
                           [:button {:on-click #(reset! error nil)} "Try again (reload page)"]]
                          comp))})))

Franklin08:12:08

and the component where I'm using it

(defn dataview-component
  []
  [error-boundary
   [:<> [dataview-menu]
    (let [{path :path, :as match-data} @match]
        (throw (js/Error. "error info"))
      [:div#content.full-page-fixed
       [:div.container-wide
        [:div#dataset-view.cfix
         [:div.tab-container.dataset-tabs [tab-bar path]
          (when match-data
            (let [view (:view (:data match-data))] [view match-data]))]]]])]])

Franklin08:12:42

I expected the fallback UI to be rendered but not nothing is rendered

Franklin08:12:49

what could I be misssing?

p-himik08:12:47

I use the exact same thing but with one potentially crucial difference - my error boundary is not something that can accept a child. But rather, it's the main component itself. In you case, I think if something goes wrong in dataview-component itself, the error boundary will not work - simply because it didn't even have the chance to be turned into an element yet.

p-himik08:12:12

Ah, and you do throw in that component - so that's why. I don't think an error boundary would catch anything in that case, as you yourself observe. Try moving its child into its own component and then using something like

[error-boundary [child-that-throws]]

👍 1
Franklin09:12:56

the part I don't undestand in your explanation here is > simply because it didn't even have the chance to be turned into an element yet

Franklin09:12:30

I wonder if there's documentation or code I can read that would help me understand better when things are turned into elements

p-himik09:12:11

For a Hiccup vector to become a React element, Reagent has to get that vector first. And your function simply does not return a vector - it throws immediately. The error boundary component is never encoded in an element, it's never instantiated. So it can't catch anything.

Franklin09:12:10

uuuh... that makes perfect sense, thanks

👍 1
p-himik09:12:46

Well, the best starting point is to read React's documentation on the difference between components, elements, and instances. Once that understanding is achieved, perhaps Reagent documentation has something about turning Hiccup into elements. I've been using Reagent for quite some time now so at this point I more often just read the source code itself.

Franklin09:12:40

'reading source code itself' 😂 ... I wonder how long it'll take for me to get there

p-himik09:12:26

Not long at all if you start doing that early. :) Besides its reactive part (stuff in the reagent.ratom namespace), it's rather easy to follow actually.

🙂 1
p-himik09:12:47

But definitely go through all the documentation first.

metehan09:12:02

how to use hooks?

p-himik09:12:08

It's documented.

metehan09:12:38

oh you replied very fast I was writing rest of the question here 😄

metehan09:12:52

I am going to check now

metehan10:12:56

I am trying to convert a javascript sample This is the original code

function MyComponent() {
  const map = useMapEvents({
    click: () => {
      map.locate()
    },
    locationfound: (location) => {
      console.log('location found:', location)
    },
  })
  return null
}

function MyMapComponent() {
  return (
    <MapContainer center={[50.5, 30.5]} zoom={13}>
      <MyComponent />
    </MapContainer>
  )
}
and this is my code I guess I am using reagent.core/as-element wrong way
(ns tools.adapters.leaflet
  (:require
   [reagent.core :refer [adapt-react-class as-element]]
   ["react-leaflet" :as leaf]))

(def map-container (adapt-react-class leaf/MapContainer))
(defn map-events [e] (as-element (leaf/useMapEvents e)))

(defn maplayer []
  [map-container {:center [51.505, -0.09] :zoom 13 :style {:height "300px"}}
   [tile-layer {:url "https://{s}.}]
   [map-events {:click #(js/console.log %)}]])
Sample source https://react-leaflet.js.org/docs/api-map/#usemapevents

p-himik10:12:17

Have you read the documentation I mentioned? as-element is, as it states in its docstring, a function to convert a Hiccup vector into a React element. But you're passing a result of a hook call to it, which cannot possibly be a Hiccup vector in this case. You need to create a function component, use a hook there just as you would in React, and then use that function component.

metehan16:12:57

I see. I did read the documentation but I couldn't figure it out. I never used react before clojurescript maybe that why I am failing to figure it out. I'll try as you suggest.

p-himik16:12:26

Ah, yeah. Reagent relies on React in more than just its internal workings - it's a semipermeable abstraction, so knowing a fair bit of React will significantly increase your chances of overall success with Reagent. It also has a very nice documentation, so I would definitely recommend reading it when time allows.

metehan16:12:37

thank you 🙂 I'll definitely read it all.

👍 1