Skip to content

Commit

Permalink
using ErrorBoundary to catch frontend errors work in progress
Browse files Browse the repository at this point in the history
  • Loading branch information
maximvlah committed Jun 20, 2024
1 parent 22dbcc3 commit 485cd7f
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 1 deletion.
4 changes: 4 additions & 0 deletions reflex/.templates/jinja/web/pages/utils.js.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,20 @@
{# Args: #}
{# component: component dictionary #}
{% macro render_self_close_tag(component) %}
{% if component.useErrorBoundary %} <ErrorBoundary onError={logFrontendError} FallbackComponent={Fallback}>{% endif %}
{%- if component.name|length %}
<{{ component.name }} {{- render_props(component.props) }}{% if component.autofocus %} ref={focusRef} {% endif %}/>
{%- else %}
{{- component.contents }}
{%- endif %}
{% if component.useErrorBoundary %} </ErrorBoundary> {% endif %}
{% endmacro %}

{# Rendering close tag with args and props. #}
{# Args: #}
{# component: component dictionary #}
{% macro render_tag(component) %}
{% if component.useErrorBoundary %} <ErrorBoundary onError={logFrontendError} FallbackComponent={Fallback}>{% endif %}
<{{component.name}} {{- render_props(component.props) }}>
{%- if component.args is not none -%}
{{- render_arg_content(component) }}
Expand All @@ -45,6 +48,7 @@
{% endfor %}
{%- endif -%}
</{{component.name}}>
{% if component.useErrorBoundary %} </ErrorBoundary> {% endif %}
{%- endmacro %}


Expand Down
4 changes: 4 additions & 0 deletions reflex/components/base/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
"Fragment",
"fragment",
],
"error_boundary": [
"ErrorBoundary",
"error_boundary",
],
"head": [
"head",
"Head",
Expand Down
27 changes: 27 additions & 0 deletions reflex/components/base/error_boundary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""A React Error Boundary component that catches unhandled frontend exceptions."""
from reflex.components.component import Component

# from reflex.constants import Hooks
# import reflex as rx


class ErrorBoundary(Component):
"""A React Error Boundary component that catches unhandled frontend exceptions."""

library = "react-error-boundary"
tag = "ErrorBoundary"

# fallback: rx.Var[Component] = rx.button("Reload Page", on_click=lambda: rx.event.call_script("window.location.reload()"))

# on_error: rx.EventHandler[lambda error, info: [info]] = rx.EventHandler(fn=lambda error, info: [info], state_full_name="state")

# def _get_events_hooks(self) -> dict[str, None]:
# """Get the hooks required by events referenced in this component.

# Returns:
# The hooks for the events.
# """
# return {Hooks.EVENTS: None}


error_boundary = ErrorBoundary.create
12 changes: 11 additions & 1 deletion reflex/components/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ class Component(BaseComponent, ABC):
# Whether the component should take the focus once the page is loaded
autofocus: bool = False

useErrorBoundary: bool = False

# components that cannot be children
_invalid_children: List[str] = []

Expand Down Expand Up @@ -937,6 +939,7 @@ def render(self) -> Dict:
props=tag.format_props(),
),
autofocus=self.autofocus,
useErrorBoundary=self.useErrorBoundary,
)
self._replace_prop_names(rendered_dict)
return rendered_dict
Expand Down Expand Up @@ -1317,6 +1320,8 @@ def _get_imports(self) -> imports.ImportDict:
# Get static imports required for event processing.
event_imports = Imports.EVENTS if self.event_triggers else {}

errors_imports = Imports.FRONTEND_ERRORS if self.useErrorBoundary else {}

# Collect imports from Vars used directly by this component.
var_imports = [
var._var_data.imports for var in self._get_vars() if var._var_data
Expand Down Expand Up @@ -1348,6 +1353,7 @@ def _make_list(
self._get_hooks_imports(),
_imports,
event_imports,
errors_imports,
*var_imports,
*_added_import_dicts,
)
Expand Down Expand Up @@ -1417,7 +1423,11 @@ def _get_events_hooks(self) -> dict[str, None]:
Returns:
The hooks for the events.
"""
return {Hooks.EVENTS: None} if self.event_triggers else {}
hooks = {Hooks.EVENTS: None} if self.event_triggers else {}

if self.useErrorBoundary:
hooks[Hooks.FRONTEND_ERRORS] = None
return hooks

def _get_special_hooks(self) -> dict[str, None]:
"""Get the hooks required by special actions referenced in this component.
Expand Down
29 changes: 29 additions & 0 deletions reflex/constants/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,40 @@ class Imports(SimpleNamespace):
f"/{Dirs.STATE_PATH}": [ImportVar(tag=CompileVars.TO_EVENT)],
}

FRONTEND_ERRORS = {
"react-error-boundary": [ImportVar(tag="ErrorBoundary")],
}


class Hooks(SimpleNamespace):
"""Common sets of hook declarations."""

EVENTS = f"const [{CompileVars.ADD_EVENTS}, {CompileVars.CONNECT_ERROR}] = useContext(EventLoopContext);"

FRONTEND_ERRORS = f"""
function Fallback({{ error, resetErrorBoundary }}) {{
return (
<div role="alert">
<h2>Unknown Error:</h2>
<p>{{ error.message }}</p>
</div>
);
}}
const [{CompileVars.ADD_EVENTS}, {CompileVars.CONNECT_ERROR}] = useContext(EventLoopContext);
const logFrontendError = (error, info) => {{
if (process.env.NODE_ENV === "production") {{
addEvents([Event("frontend_event_exception_state.handle_frontend_exception", {{
stack: error.stack,
}})])
}}
}}
"""

AUTOFOCUS = """
// Set focus to the specified element.
const focusRef = useRef(null)
Expand Down
1 change: 1 addition & 0 deletions reflex/constants/installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ class Commands(SimpleNamespace):
"react-focus-lock": "2.11.3",
"socket.io-client": "4.6.1",
"universal-cookie": "4.0.4",
"react-error-boundary": "4.0.13",
}
DEV_DEPENDENCIES = {
"autoprefixer": "10.4.14",
Expand Down
15 changes: 15 additions & 0 deletions tests/components/test_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -647,27 +647,33 @@ def test_component_create_unallowed_types(children, test_component):
"special_props": set(),
"children": [],
"autofocus": False,
"useErrorBoundary": False,
}
],
"autofocus": False,
"useErrorBoundary": False,
}
],
"autofocus": False,
"useErrorBoundary": False,
},
),
(
(rx.text("first_text"), rx.text("second_text")),
{
"args": None,
"autofocus": False,
"useErrorBoundary": False,
"children": [
{
"args": None,
"autofocus": False,
"useErrorBoundary": False,
"children": [
{
"args": None,
"autofocus": False,
"useErrorBoundary": False,
"children": [],
"contents": "{`first_text`}",
"name": "",
Expand All @@ -683,10 +689,12 @@ def test_component_create_unallowed_types(children, test_component):
{
"args": None,
"autofocus": False,
"useErrorBoundary": False,
"children": [
{
"args": None,
"autofocus": False,
"useErrorBoundary": False,
"children": [],
"contents": "{`second_text`}",
"name": "",
Expand All @@ -711,14 +719,17 @@ def test_component_create_unallowed_types(children, test_component):
{
"args": None,
"autofocus": False,
"useErrorBoundary": False,
"children": [
{
"args": None,
"autofocus": False,
"useErrorBoundary": False,
"children": [
{
"args": None,
"autofocus": False,
"useErrorBoundary": False,
"children": [],
"contents": "{`first_text`}",
"name": "",
Expand All @@ -734,18 +745,22 @@ def test_component_create_unallowed_types(children, test_component):
{
"args": None,
"autofocus": False,
"useErrorBoundary": False,
"children": [
{
"args": None,
"autofocus": False,
"useErrorBoundary": False,
"children": [
{
"args": None,
"autofocus": False,
"useErrorBoundary": False,
"children": [
{
"args": None,
"autofocus": False,
"useErrorBoundary": False,
"children": [],
"contents": "{`second_text`}",
"name": "",
Expand Down

0 comments on commit 485cd7f

Please sign in to comment.