Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add type hinting to error boundary #4182

Merged
merged 4 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -40,6 +40,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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does the component_stack get used for? it's not being passed to the registered exception handler

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not being used here, but it's provided by the react library


"""
app_instance = getattr(prerequisites.get_app(), constants.CompileVars.APP)
Expand Down
Loading