Explorar o código

add type hinting to error boundary (#4182)

* add type hinting to error boundary

* remove logFrontendError

* fix other calls to handle_frontend_exception
Khaleel Al-Adhami hai 7 meses
pai
achega
45959881ac

+ 2 - 0
reflex/.templates/web/utils/state.js

@@ -743,6 +743,7 @@ export const useEventLoop = (
       addEvents([
       addEvents([
         Event(`${exception_state_name}.handle_frontend_exception`, {
         Event(`${exception_state_name}.handle_frontend_exception`, {
           stack: error.stack,
           stack: error.stack,
+          component_stack: "",
         }),
         }),
       ]);
       ]);
       return false;
       return false;
@@ -754,6 +755,7 @@ export const useEventLoop = (
       addEvents([
       addEvents([
         Event(`${exception_state_name}.handle_frontend_exception`, {
         Event(`${exception_state_name}.handle_frontend_exception`, {
           stack: event.reason.stack,
           stack: event.reason.stack,
+          component_stack: "",
         }),
         }),
       ]);
       ]);
       return false;
       return false;

+ 35 - 24
reflex/components/base/error_boundary.py

@@ -2,16 +2,30 @@
 
 
 from __future__ import annotations
 from __future__ import annotations
 
 
-from typing import List
+from typing import Dict, List, Tuple
 
 
 from reflex.compiler.compiler import _compile_component
 from reflex.compiler.compiler import _compile_component
 from reflex.components.component import Component
 from reflex.components.component import Component
 from reflex.components.el import div, p
 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.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):
 class ErrorBoundary(Component):
@@ -21,31 +35,13 @@ class ErrorBoundary(Component):
     tag = "ErrorBoundary"
     tag = "ErrorBoundary"
 
 
     # Fired when the boundary catches an error.
     # 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.
     # Rendered instead of the children when an error is caught.
     Fallback_component: Var[Component] = Var(_js_expr="Fallback")._replace(
     Fallback_component: Var[Component] = Var(_js_expr="Fallback")._replace(
         _var_type=Component
         _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]:
     def add_custom_code(self) -> List[str]:
         """Add custom Javascript code into the page that contains this component.
         """Add custom Javascript code into the page that contains this component.
 
 
@@ -75,5 +71,20 @@ class ErrorBoundary(Component):
             """
             """
         ]
         ]
 
 
+    @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
 error_boundary = ErrorBoundary.create

+ 8 - 7
reflex/components/base/error_boundary.pyi

@@ -3,17 +3,18 @@
 # ------------------- DO NOT EDIT ----------------------
 # ------------------- DO NOT EDIT ----------------------
 # This file was generated by `reflex/utils/pyi_generator.py`!
 # 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.components.component import Component
 from reflex.event import EventType
 from reflex.event import EventType
 from reflex.style import Style
 from reflex.style import Style
-from reflex.utils.imports import ImportVar
 from reflex.vars.base import Var
 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):
 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]: ...
     def add_custom_code(self) -> List[str]: ...
     @overload
     @overload
     @classmethod
     @classmethod
@@ -31,7 +32,7 @@ class ErrorBoundary(Component):
         on_click: Optional[EventType[[]]] = None,
         on_click: Optional[EventType[[]]] = None,
         on_context_menu: Optional[EventType[[]]] = None,
         on_context_menu: Optional[EventType[[]]] = None,
         on_double_click: 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_focus: Optional[EventType[[]]] = None,
         on_mount: Optional[EventType[[]]] = None,
         on_mount: Optional[EventType[[]]] = None,
         on_mouse_down: Optional[EventType[[]]] = None,
         on_mouse_down: Optional[EventType[[]]] = None,
@@ -45,7 +46,7 @@ class ErrorBoundary(Component):
         on_unmount: Optional[EventType[[]]] = None,
         on_unmount: Optional[EventType[[]]] = None,
         **props,
         **props,
     ) -> "ErrorBoundary":
     ) -> "ErrorBoundary":
-        """Create the component.
+        """Create an ErrorBoundary component.
 
 
         Args:
         Args:
             *children: The children of the component.
             *children: The children of the component.
@@ -59,7 +60,7 @@ class ErrorBoundary(Component):
             **props: The props of the component.
             **props: The props of the component.
 
 
         Returns:
         Returns:
-            The component.
+            The ErrorBoundary component.
         """
         """
         ...
         ...
 
 

+ 0 - 10
reflex/constants/compiler.py

@@ -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):
 class MemoizationDisposition(enum.Enum):
     """The conditions under which a component should be memoized."""
     """The conditions under which a component should be memoized."""

+ 4 - 1
reflex/state.py

@@ -39,6 +39,7 @@ from typing import (
 from sqlalchemy.orm import DeclarativeBase
 from sqlalchemy.orm import DeclarativeBase
 from typing_extensions import Self
 from typing_extensions import Self
 
 
+from reflex import event
 from reflex.config import get_config
 from reflex.config import get_config
 from reflex.istate.data import RouterData
 from reflex.istate.data import RouterData
 from reflex.vars.base import (
 from reflex.vars.base import (
@@ -2094,7 +2095,8 @@ class State(BaseState):
 class FrontendEventExceptionState(State):
 class FrontendEventExceptionState(State):
     """Substate for handling frontend exceptions."""
     """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.
         """Handle frontend exceptions.
 
 
         If a frontend exception handler is provided, it will be called.
         If a frontend exception handler is provided, it will be called.
@@ -2102,6 +2104,7 @@ class FrontendEventExceptionState(State):
 
 
         Args:
         Args:
             stack: The stack trace of the exception.
             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)
         app_instance = getattr(prerequisites.get_app(), constants.CompileVars.APP)