Skip to content

Commit

Permalink
add type hinting to error boundary (#4182)
Browse files Browse the repository at this point in the history
* add type hinting to error boundary

* remove logFrontendError

* fix other calls to handle_frontend_exception
  • Loading branch information
adhami3310 authored Oct 22, 2024
1 parent 54ad9f0 commit 4595988
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 42 deletions.
2 changes: 2 additions & 0 deletions reflex/.templates/web/utils/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,7 @@ export const useEventLoop = (
addEvents([
Event(`${exception_state_name}.handle_frontend_exception`, {
stack: error.stack,
component_stack: "",
}),
]);
return false;
Expand All @@ -754,6 +755,7 @@ export const useEventLoop = (
addEvents([
Event(`${exception_state_name}.handle_frontend_exception`, {
stack: event.reason.stack,
component_stack: "",
}),
]);
return false;
Expand Down
59 changes: 35 additions & 24 deletions reflex/components/base/error_boundary.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,30 @@

from __future__ import annotations

from typing import List
from typing import Dict, List, Tuple

from reflex.compiler.compiler import _compile_component
from reflex.components.component import Component
from reflex.components.el import div, p
from reflex.constants import Hooks, Imports
from reflex.event import EventChain, EventHandler
from reflex.utils.imports import ImportVar
from reflex.event import EventHandler
from reflex.state import FrontendEventExceptionState
from reflex.vars.base import Var
from reflex.vars.function import FunctionVar


def on_error_spec(error: Var, info: Var[Dict[str, str]]) -> Tuple[Var[str], Var[str]]:
"""The spec for the on_error event handler.
Args:
error: The error message.
info: Additional information about the error.
Returns:
The arguments for the event handler.
"""
return (
error.stack,
info.componentStack,
)


class ErrorBoundary(Component):
Expand All @@ -21,31 +35,13 @@ class ErrorBoundary(Component):
tag = "ErrorBoundary"

# Fired when the boundary catches an error.
on_error: EventHandler[lambda error, info: [error, info]] = Var( # type: ignore
"logFrontendError"
).to(FunctionVar, EventChain)
on_error: EventHandler[on_error_spec]

# Rendered instead of the children when an error is caught.
Fallback_component: Var[Component] = Var(_js_expr="Fallback")._replace(
_var_type=Component
)

def add_imports(self) -> dict[str, list[ImportVar]]:
"""Add imports for the component.
Returns:
The imports to add.
"""
return Imports.EVENTS

def add_hooks(self) -> List[str | Var]:
"""Add hooks for the component.
Returns:
The hooks to add.
"""
return [Hooks.EVENTS, Hooks.FRONTEND_ERRORS]

def add_custom_code(self) -> List[str]:
"""Add custom Javascript code into the page that contains this component.
Expand Down Expand Up @@ -75,5 +71,20 @@ def add_custom_code(self) -> List[str]:
"""
]

@classmethod
def create(cls, *children, **props):
"""Create an ErrorBoundary component.
Args:
*children: The children of the component.
**props: The props of the component.
Returns:
The ErrorBoundary component.
"""
if "on_error" not in props:
props["on_error"] = FrontendEventExceptionState.handle_frontend_exception
return super().create(*children, **props)


error_boundary = ErrorBoundary.create
15 changes: 8 additions & 7 deletions reflex/components/base/error_boundary.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@
# ------------------- DO NOT EDIT ----------------------
# This file was generated by `reflex/utils/pyi_generator.py`!
# ------------------------------------------------------
from typing import Any, Dict, List, Optional, Union, overload
from typing import Any, Dict, List, Optional, Tuple, Union, overload

from reflex.components.component import Component
from reflex.event import EventType
from reflex.style import Style
from reflex.utils.imports import ImportVar
from reflex.vars.base import Var

def on_error_spec(
error: Var, info: Var[Dict[str, str]]
) -> Tuple[Var[str], Var[str]]: ...

class ErrorBoundary(Component):
def add_imports(self) -> dict[str, list[ImportVar]]: ...
def add_hooks(self) -> List[str | Var]: ...
def add_custom_code(self) -> List[str]: ...
@overload
@classmethod
Expand All @@ -31,7 +32,7 @@ class ErrorBoundary(Component):
on_click: Optional[EventType[[]]] = None,
on_context_menu: Optional[EventType[[]]] = None,
on_double_click: Optional[EventType[[]]] = None,
on_error: Optional[EventType] = None,
on_error: Optional[EventType[str, str]] = None,
on_focus: Optional[EventType[[]]] = None,
on_mount: Optional[EventType[[]]] = None,
on_mouse_down: Optional[EventType[[]]] = None,
Expand All @@ -45,7 +46,7 @@ class ErrorBoundary(Component):
on_unmount: Optional[EventType[[]]] = None,
**props,
) -> "ErrorBoundary":
"""Create the component.
"""Create an ErrorBoundary component.
Args:
*children: The children of the component.
Expand All @@ -59,7 +60,7 @@ class ErrorBoundary(Component):
**props: The props of the component.
Returns:
The component.
The ErrorBoundary component.
"""
...

Expand Down
10 changes: 0 additions & 10 deletions reflex/constants/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,16 +132,6 @@ class Hooks(SimpleNamespace):
}
})"""

FRONTEND_ERRORS = f"""
const logFrontendError = (error, info) => {{
if (process.env.NODE_ENV === "production") {{
addEvents([Event("{CompileVars.FRONTEND_EXCEPTION_STATE_FULL}.handle_frontend_exception", {{
stack: error.stack,
}})])
}}
}}
"""


class MemoizationDisposition(enum.Enum):
"""The conditions under which a component should be memoized."""
Expand Down
5 changes: 4 additions & 1 deletion reflex/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from sqlalchemy.orm import DeclarativeBase
from typing_extensions import Self

from reflex import event
from reflex.config import get_config
from reflex.istate.data import RouterData
from reflex.vars.base import (
Expand Down Expand Up @@ -2094,14 +2095,16 @@ class State(BaseState):
class FrontendEventExceptionState(State):
"""Substate for handling frontend exceptions."""

def handle_frontend_exception(self, stack: str) -> None:
@event
def handle_frontend_exception(self, stack: str, component_stack: str) -> None:
"""Handle frontend exceptions.
If a frontend exception handler is provided, it will be called.
Otherwise, the default frontend exception handler will be called.
Args:
stack: The stack trace of the exception.
component_stack: The stack trace of the component where the exception occurred.
"""
app_instance = getattr(prerequisites.get_app(), constants.CompileVars.APP)
Expand Down

0 comments on commit 4595988

Please sign in to comment.