Fix the error boundary (#3637)

* - added possibility to pass front- and backend exception handlers to rx.App
- wrapped the app with ErrorBoundary component to catch rendering errors

* added missing exception handler to reflex.app._process

* regenerated pyi

* fix unit tests for exception handler validation

* fix typing error in ErrorBoudary

* - fix error_bounday pyi
- minor refactoring of error boundary
- removed error boundary from 404 page

* added missing inspect module import after merging main

* fix typing error

* Clean up ErrorBoundary component

* Remove custom _render function
* Remove special imports in Component base class
* Simplify hooks for better composability

* test_exception_handlers: also test in prod mode

* fixed color prop

* fixed error boundary bug and removed hardcoding of the frontend event exception state

* formatted the code after conflict resolution

* fixed type of the error_boundary

* ruff format

---------

Co-authored-by: Maxim Vlah <m.vlah@senbax.de>
Co-authored-by: Masen Furer <m_github@0x26.net>
This commit is contained in:
Maxim Vlah 2024-07-09 20:19:14 +02:00 committed by GitHub
parent 5071245127
commit fa71edd224
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 22 additions and 15 deletions

View File

@ -26,6 +26,8 @@ export const clientStorage = {}
{% if state_name %}
export const state_name = "{{state_name}}"
export const exception_state_name = "{{const.frontend_exception_state}}"
// Theses events are triggered on initial load and each page navigation.
export const onLoadInternalEvent = () => {
const internal_events = [];

View File

@ -11,6 +11,7 @@ import {
initialState,
onLoadInternalEvent,
state_name,
exception_state_name,
} from "utils/context.js";
import debounce from "/utils/helpers/debounce";
import throttle from "/utils/helpers/throttle";
@ -698,7 +699,7 @@ export const useEventLoop = (
}
window.onerror = function (msg, url, lineNo, columnNo, error) {
addEvents([Event("state.frontend_event_exception_state.handle_frontend_exception", {
addEvents([Event(`${exception_state_name}.handle_frontend_exception`, {
stack: error.stack,
})])
return false;
@ -707,7 +708,7 @@ export const useEventLoop = (
//NOTE: Only works in Chrome v49+
//https://github.com/mknichel/javascript-errors?tab=readme-ov-file#promise-rejection-events
window.onunhandledrejection = function (event) {
addEvents([Event("state.frontend_event_exception_state.handle_frontend_exception", {
addEvents([Event(`${exception_state_name}.handle_frontend_exception`, {
stack: event.reason.stack,
})])
return false;

View File

@ -132,14 +132,17 @@ def default_overlay_component() -> Component:
)
def default_error_boundary() -> Component:
def default_error_boundary(*children: Component) -> Component:
"""Default error_boundary attribute for App.
Args:
*children: The children to render in the error boundary.
Returns:
The default error_boundary, which is an ErrorBoundary.
"""
return ErrorBoundary.create()
return ErrorBoundary.create(*children)
class OverlayFragment(Fragment):
@ -184,9 +187,7 @@ class App(MiddlewareMixin, LifespanMixin, Base):
)
# Error boundary component to wrap the app with.
error_boundary: Optional[Union[Component, ComponentCallable]] = (
default_error_boundary
)
error_boundary: Optional[ComponentCallable] = default_error_boundary
# Components to add to the head of every page.
head_components: List[Component] = []
@ -751,7 +752,7 @@ class App(MiddlewareMixin, LifespanMixin, Base):
if self.error_boundary is None:
return component
component = ErrorBoundary.create(*component.children)
component = self.error_boundary(*component.children)
return component

View File

@ -44,6 +44,7 @@ class ReflexJinjaEnvironment(Environment):
"hydrate": constants.CompileVars.HYDRATE,
"on_load_internal": constants.CompileVars.ON_LOAD_INTERNAL,
"update_vars_internal": constants.CompileVars.UPDATE_VARS_INTERNAL,
"frontend_exception_state": constants.CompileVars.FRONTEND_EXCEPTION_STATE,
}

View File

@ -65,6 +65,8 @@ class CompileVars(SimpleNamespace):
ON_LOAD_INTERNAL = "on_load_internal_state.on_load_internal"
# The name of the internal event to update generic state vars.
UPDATE_VARS_INTERNAL = "update_vars_internal_state.update_vars_internal"
# The name of the frontend event exception state
FRONTEND_EXCEPTION_STATE = "state.frontend_event_exception_state"
class PageNames(SimpleNamespace):
@ -124,14 +126,14 @@ class Hooks(SimpleNamespace):
}
})"""
FRONTEND_ERRORS = """
const logFrontendError = (error, info) => {
if (process.env.NODE_ENV === "production") {
addEvents([Event("frontend_event_exception_state.handle_frontend_exception", {
FRONTEND_ERRORS = f"""
const logFrontendError = (error, info) => {{
if (process.env.NODE_ENV === "production") {{
addEvents([Event("{CompileVars.FRONTEND_EXCEPTION_STATE}.handle_frontend_exception", {{
stack: error.stack,
})])
}
}
}})])
}}
}}
"""