This commit is contained in:
Khaleel Al-Adhami 2024-10-21 18:18:27 -07:00
commit 97430f1e3a
7 changed files with 100 additions and 65 deletions

View File

@ -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;

View File

@ -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 EventHandler
from reflex.event import EventChain, EventHandler from reflex.state import FrontendEventExceptionState
from reflex.utils.imports import ImportVar
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 on_error: EventHandler[on_error_spec]
"logFrontendError"
).to(FunctionVar, EventChain)
# 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

View File

@ -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.
""" """
... ...

View File

@ -3,11 +3,11 @@
from __future__ import annotations from __future__ import annotations
import enum import enum
from typing import Dict, List, Literal, Optional, Union from typing import Dict, List, Literal, Optional, Tuple, Union
from reflex.base import Base from reflex.base import Base
from reflex.components.component import Component, NoSSRComponent from reflex.components.component import Component, NoSSRComponent
from reflex.event import EventHandler from reflex.event import EventHandler, empty_event, identity_event
from reflex.utils.format import to_camel_case from reflex.utils.format import to_camel_case
from reflex.utils.imports import ImportDict, ImportVar from reflex.utils.imports import ImportDict, ImportVar
from reflex.vars.base import Var from reflex.vars.base import Var
@ -68,6 +68,35 @@ class EditorOptions(Base):
button_list: Optional[List[Union[List[str], str]]] button_list: Optional[List[Union[List[str], str]]]
def on_blur_spec(e: Var, content: Var[str]) -> Tuple[Var[str]]:
"""A helper function to specify the on_blur event handler.
Args:
e: The event.
content: The content of the editor.
Returns:
A tuple containing the content of the editor.
"""
return (content,)
def on_paste_spec(
e: Var, clean_data: Var[str], max_char_count: Var[bool]
) -> Tuple[Var[str], Var[bool]]:
"""A helper function to specify the on_paste event handler.
Args:
e: The event.
clean_data: The clean data.
max_char_count: The maximum character count.
Returns:
A tuple containing the clean data and the maximum character count.
"""
return (clean_data, max_char_count)
class Editor(NoSSRComponent): class Editor(NoSSRComponent):
"""A Rich Text Editor component based on SunEditor. """A Rich Text Editor component based on SunEditor.
Not every JS prop is listed here (some are not easily usable from python), Not every JS prop is listed here (some are not easily usable from python),
@ -178,36 +207,31 @@ class Editor(NoSSRComponent):
disable_toolbar: Var[bool] disable_toolbar: Var[bool]
# Fired when the editor content changes. # Fired when the editor content changes.
on_change: EventHandler[lambda content: [content]] on_change: EventHandler[identity_event(str)]
# Fired when the something is inputted in the editor. # Fired when the something is inputted in the editor.
on_input: EventHandler[lambda e: [e]] on_input: EventHandler[empty_event]
# Fired when the editor loses focus. # Fired when the editor loses focus.
on_blur: EventHandler[lambda e, content: [content]] on_blur: EventHandler[on_blur_spec]
# Fired when the editor is loaded. # Fired when the editor is loaded.
on_load: EventHandler[lambda reload: [reload]] on_load: EventHandler[identity_event(bool)]
# Fired when the editor is resized.
on_resize_editor: EventHandler[lambda height, prev_height: [height, prev_height]]
# Fired when the editor content is copied. # Fired when the editor content is copied.
on_copy: EventHandler[lambda e, clipboard_data: [clipboard_data]] on_copy: EventHandler[empty_event]
# Fired when the editor content is cut. # Fired when the editor content is cut.
on_cut: EventHandler[lambda e, clipboard_data: [clipboard_data]] on_cut: EventHandler[empty_event]
# Fired when the editor content is pasted. # Fired when the editor content is pasted.
on_paste: EventHandler[ on_paste: EventHandler[on_paste_spec]
lambda e, clean_data, max_char_count: [clean_data, max_char_count]
]
# Fired when the code view is toggled. # Fired when the code view is toggled.
toggle_code_view: EventHandler[lambda is_code_view: [is_code_view]] toggle_code_view: EventHandler[identity_event(bool)]
# Fired when the full screen mode is toggled. # Fired when the full screen mode is toggled.
toggle_full_screen: EventHandler[lambda is_full_screen: [is_full_screen]] toggle_full_screen: EventHandler[identity_event(bool)]
def add_imports(self) -> ImportDict: def add_imports(self) -> ImportDict:
"""Add imports for the Editor component. """Add imports for the Editor component.

View File

@ -4,7 +4,7 @@
# This file was generated by `reflex/utils/pyi_generator.py`! # This file was generated by `reflex/utils/pyi_generator.py`!
# ------------------------------------------------------ # ------------------------------------------------------
import enum import enum
from typing import Any, Dict, List, Literal, Optional, Union, overload from typing import Any, Dict, List, Literal, Optional, Tuple, Union, overload
from reflex.base import Base from reflex.base import Base
from reflex.components.component import NoSSRComponent from reflex.components.component import NoSSRComponent
@ -44,6 +44,11 @@ class EditorOptions(Base):
rtl: Optional[bool] rtl: Optional[bool]
button_list: Optional[List[Union[List[str], str]]] button_list: Optional[List[Union[List[str], str]]]
def on_blur_spec(e: Var, content: Var[str]) -> Tuple[Var[str]]: ...
def on_paste_spec(
e: Var, clean_data: Var[str], max_char_count: Var[bool]
) -> Tuple[Var[str], Var[bool]]: ...
class Editor(NoSSRComponent): class Editor(NoSSRComponent):
def add_imports(self) -> ImportDict: ... def add_imports(self) -> ImportDict: ...
@overload @overload
@ -122,15 +127,15 @@ class Editor(NoSSRComponent):
class_name: Optional[Any] = None, class_name: Optional[Any] = None,
autofocus: Optional[bool] = None, autofocus: Optional[bool] = None,
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None, custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
on_blur: Optional[EventType] = None, on_blur: Optional[EventType[str]] = None,
on_change: Optional[EventType] = None, on_change: Optional[EventType] = None,
on_click: Optional[EventType[[]]] = None, on_click: Optional[EventType[[]]] = None,
on_context_menu: Optional[EventType[[]]] = None, on_context_menu: Optional[EventType[[]]] = None,
on_copy: Optional[EventType] = None, on_copy: Optional[EventType[[]]] = None,
on_cut: Optional[EventType] = None, on_cut: Optional[EventType[[]]] = None,
on_double_click: Optional[EventType[[]]] = None, on_double_click: Optional[EventType[[]]] = None,
on_focus: Optional[EventType[[]]] = None, on_focus: Optional[EventType[[]]] = None,
on_input: Optional[EventType] = None, on_input: Optional[EventType[[]]] = None,
on_load: Optional[EventType] = None, on_load: 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,
@ -140,8 +145,7 @@ class Editor(NoSSRComponent):
on_mouse_out: Optional[EventType[[]]] = None, on_mouse_out: Optional[EventType[[]]] = None,
on_mouse_over: Optional[EventType[[]]] = None, on_mouse_over: Optional[EventType[[]]] = None,
on_mouse_up: Optional[EventType[[]]] = None, on_mouse_up: Optional[EventType[[]]] = None,
on_paste: Optional[EventType] = None, on_paste: Optional[EventType[str, bool]] = None,
on_resize_editor: Optional[EventType] = None,
on_scroll: Optional[EventType[[]]] = None, on_scroll: Optional[EventType[[]]] = None,
on_unmount: Optional[EventType[[]]] = None, on_unmount: Optional[EventType[[]]] = None,
toggle_code_view: Optional[EventType] = None, toggle_code_view: Optional[EventType] = None,

View File

@ -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."""

View File

@ -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)