Replace component stack with just component name in error messages

- Completely remove component-path function
- Remove component stack information from error messages
- Test that component stack information is availble using Error Boundary
This commit is contained in:
Juho Teperi 2020-02-28 14:39:33 +02:00
parent 47b433f217
commit 5ead085c93
4 changed files with 33 additions and 63 deletions

View File

@ -4,13 +4,15 @@
- Removed deprecated namespaces/function/macros: - Removed deprecated namespaces/function/macros:
- Removed `reagent.interop` namespace (macros `$`, `$!`, `unchecked-aget` and `unchecked-aset`) - Removed `reagent.interop` namespace (macros `$`, `$!`, `unchecked-aget` and `unchecked-aset`)
- Deprecated functions:
- `reagent.core/component-path` (Reason: implementation depends on internal React - `reagent.core/component-path` (Reason: implementation depends on internal React
details and using just Component `displayName` achieves nearly the same) details and using just Component `displayName` achieves nearly the same)
- All DOM related functions (notably `render` and `dom-node`) have been removed - All DOM related functions (notably `render` and `dom-node`) have been removed
from `reagent.core` namespace and are now only available in `reagent.dom` namespace. from `reagent.core` namespace and are now only available in `reagent.dom` namespace.
This is to make non-DOM environments (React-native) first class targets with Reagent, This is to make non-DOM environments (React-native) first class targets with Reagent,
as requiring `react-dom` always causes problems in such environments. as requiring `react-dom` always causes problems in such environments.
- `Error rendering component` messages no longer contain component stack information.
Instead one should use [an Error Boundary](https://reactjs.org/docs/error-boundaries.html#component-stack-traces)
to catch the problem and look at the error information `componentStack` property.
## 0.9.1 (2020-01-15) ## 0.9.1 (2020-01-15)

View File

@ -345,11 +345,3 @@
"Works just like clojure.core/partial, but the result can be compared with =" "Works just like clojure.core/partial, but the result can be compared with ="
[f & args] [f & args]
(util/make-partial-fn f args)) (util/make-partial-fn f args))
(defn component-path
"Try to return the path of component c as a string.
Maybe useful for debugging and error reporting, but may break
with future versions of React (and return nil)."
{:deprecated "1.0.0"}
[c]
(comp/component-path c))

View File

@ -120,7 +120,17 @@
(recur c)) (recur c))
:else res))) :else res)))
(declare comp-name) (defn component-name [c]
(some-> c .-constructor .-displayName))
(defn comp-name []
(if (dev?)
(let [c *current-component*
n (component-name c)]
(if-not (empty? n)
(str " (in " n ")")
""))
""))
(defn do-render [c] (defn do-render [c]
(binding [*current-component* c] (binding [*current-component* c]
@ -362,50 +372,6 @@
cmp)) cmp))
(defn fiber-component-path [fiber]
(let [name (some-> fiber
(.-type)
(.-displayName))
parent (some-> fiber
(.-return))
path (some-> parent
fiber-component-path
(str " > "))
res (str path name)]
(when-not (empty? res) res)))
(defn component-path [c]
;; Alternative branch for React 16
;; Try both original name (for UMD foreign-lib) and manged name (property access, for Closure optimized React)
(if-let [fiber (or (some-> c (gobj/get "_reactInternalFiber"))
(some-> c (.-_reactInternalFiber)))]
(fiber-component-path fiber)
(let [instance (or (some-> c (gobj/get "_reactInternalInstance"))
(some-> c (.-_reactInternalInstance))
c)
elem (or (some-> instance (gobj/get "_currentElement"))
(some-> instance (.-_currentElement)))
name (some-> elem
(.-type)
(.-displayName))
owner (or (some-> elem (gobj/get "_owner"))
(some-> elem (.-_owner)))
path (some-> owner
component-path
(str " > "))
res (str path name)]
(when-not (empty? res) res))))
(defn comp-name []
(if (dev?)
(let [c *current-component*
n (or (component-path c)
(some-> c .-constructor util/fun-name))]
(if-not (empty? n)
(str " (in " n ")")
""))
""))
(defn fn-to-class [f] (defn fn-to-class [f]
(assert-callable f) (assert-callable f)
(warn-unless (not (and (react-class? f) (warn-unless (not (and (react-class? f)

View File

@ -563,7 +563,7 @@
(wrap-capture-console-error (wrap-capture-console-error
#(with-mounted-component [c] #(with-mounted-component [c]
(fn [c div]))))] (fn [c div]))))]
(if (debug/dev?) (if (dev?)
(is (= "Warning: Every element in a seq should have a unique :key: ([:button {:on-click #object[Function]}] [:button {:on-click #object[Function]}] [:button {:on-click #object[Function]}])\n (in reagenttest.testreagent.key_tester)" (is (= "Warning: Every element in a seq should have a unique :key: ([:button {:on-click #object[Function]}] [:button {:on-click #object[Function]}] [:button {:on-click #object[Function]}])\n (in reagenttest.testreagent.key_tester)"
(first (:warn w))))))))) (first (:warn w)))))))))
@ -664,7 +664,7 @@
tc (r/create-class {:display-name "atestcomponent" tc (r/create-class {:display-name "atestcomponent"
:render (fn [] :render (fn []
(let [c (r/current-component)] (let [c (r/current-component)]
(reset! a (comp/component-path c)) (reset! a (comp/component-name c))
[:div]))})] [:div]))})]
(with-mounted-component [tc] (with-mounted-component [tc]
(fn [c] (fn [c]
@ -1013,18 +1013,19 @@
cmp) cmp)
pkg "reagenttest.testreagent." pkg "reagenttest.testreagent."
stack1 (str "in " pkg "comp1") stack1 (str "in " pkg "comp1")
stack2 (str "in " pkg "comp2 > " pkg "comp1")
re (fn [& s] re (fn [& s]
(re-pattern (apply str s))) (re-pattern (apply str s)))
rend (fn [x] rend (fn [x]
(with-mounted-component x identity))] (with-mounted-component x identity))]
;; Error is orginally caused by comp1, so only that is shown in the error
(let [e (debug/track-warnings (let [e (debug/track-warnings
(wrap-capture-window-error (wrap-capture-window-error
(wrap-capture-console-error (wrap-capture-console-error
#(is (thrown-with-msg? #(is (thrown-with-msg?
:default (re "Invalid tag: 'div.' \\(" stack2 "\\)") :default (re "Invalid tag: 'div.' \\(" stack1 "\\)")
(rend [comp2 [:div. "foo"]]))))))] (rend [comp2 [:div. "foo"]]))))))]
(is (= (str "Error rendering component (" stack2 ")") (is (= (str "Error rendering component (" stack1 ")")
(last (:error e))))) (last (:error e)))))
(let [e (debug/track-warnings (let [e (debug/track-warnings
@ -1052,9 +1053,11 @@
(deftest test-error-boundary (deftest test-error-boundary
(let [error (r/atom nil) (let [error (r/atom nil)
info (r/atom nil)
error-boundary (fn error-boundary [comp] error-boundary (fn error-boundary [comp]
(r/create-class (r/create-class
{:component-did-catch (fn [this e info]) {:component-did-catch (fn [this e i]
(reset! info i))
:get-derived-state-from-error (fn [e] :get-derived-state-from-error (fn [e]
(reset! error e) (reset! error e)
#js {}) #js {})
@ -1063,15 +1066,22 @@
[:div "Something went wrong."] [:div "Something went wrong."]
comp))})) comp))}))
comp1 (fn comp1 [] comp1 (fn comp1 []
(throw (js/Error. "Test error")))] (throw (js/Error. "Test error")))
comp2 (fn comp2 []
[comp1])]
(debug/track-warnings (debug/track-warnings
(wrap-capture-window-error (wrap-capture-window-error
(wrap-capture-console-error (wrap-capture-console-error
#(with-mounted-component [error-boundary [comp1]] #(with-mounted-component [error-boundary [comp2]]
(fn [c div] (fn [c div]
(r/flush) (r/flush)
(is (= "Test error" (.-message @error))) (is (= "Test error" (.-message @error)))
(is (re-find #"Something went wrong\." (.-innerHTML div)))))))))) (is (re-find #"Something went wrong\." (.-innerHTML div)))
(if (dev?)
(is (re-find #"\n in reagenttest.testreagent.comp1 \(created by reagenttest.testreagent.comp2\)\n in reagenttest.testreagent.comp2 \(created by reagent[0-9]+\)\n in reagent[0-9]+ \(created by reagenttest.testreagent.error_boundary\)\n in reagenttest.testreagent.error_boundary"
(.-componentStack @info)))
(is (re-find #"\n in .+\n in .+\n in reagent[0-9]+\n in .+"
(.-componentStack @info))) ))))))))
(deftest test-dom-node (deftest test-dom-node
(let [node (atom nil) (let [node (atom nil)