-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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 the possibility to pass custom front- and backend event exception handlers to the rx.App #3476
Add the possibility to pass custom front- and backend event exception handlers to the rx.App #3476
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for the contribution. i'll test it out further this week
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've started testing it on my side.
No issue with the backend error handler, seems to work properly.
I've had issue triggering it for frontend error however, seems like not all errors are being caught?
The following code still produce a frontend error, but doesn't seems to send it to the backend.
ie [Reflex Frontend Exception]
does not appear in that case.
class MockCompo(rx.Component):
tag = "UnknownTag"
@rx.page()
def index():
return rx.foreach([1, 2, 3], MockCompo.create)
Also tested with
@rx.page()
def index():
return rx.script(console.log(foo))
In this case it also doesn't triggers.
Also tested with
@rx.page()
def index():
return rx.button("Test", on_click=rx.call_script("console.log(foo)"))
In this case it does trigger the frontend handler.
Also, more importantly, I think the default handler should be tied to the Config should stay as simple as possible. We could take the PR as-is and extend it after to cover all the errors it doesn't catch at the moment, but the defaults function should be defined in the App. |
@Lendemor thanks for the feedback. I will continue working on handling aforementioned edge cases tomorrow and will keep you updated. In the meantime, I have just moved the exception handlers from rx.Config to rx.App as you demanded. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to work fine in dev mode (although a frontend error triggers the handler twice for some reason).
But in prod mode, when i hit an unhandled frontend error, it does not fire the event...
import reflex as rx
class State(rx.State):
broken: bool = False
def index() -> rx.Component:
return rx.container(
rx.vstack(
rx.cond(
State.broken,
rx.text("Broken", size=rx.Var.create("x", _var_is_local=False, _var_is_string=False)),
),
rx.text("Break the app? ", rx.switch(checked=State.broken, on_change=State.set_broken)),
spacing="5",
justify="center",
min_height="85vh",
),
rx.logo(),
)
app = rx.App()
app.add_page(index)
Flipping the switch introduces a component with a rendering error.
In dev mode, flipping the switch prints the error to the terminal, browser console, and on screen.
In prod mode, flipping the switch redirects to a page that says "Application error: a client-side exception has occurred (see the browser console for more information)."
I was able to fix the issue mentioned by @masenf with error boundary wrapper. I used the react-error-boundary lib recommended by React team: This made it catch these errors in production and render a fallback component, however in debug mode it fired the error handler thrice. So, I made the error boundary call onError only in production through checking the Also the ErrorBoundary has limitations, i.e. does not catch all unhandled errors. https://legacy.reactjs.org/docs/error-boundaries.html class ErrorBoundary(Component):
"""A React Error Boundary component that catches unhandled frontend exceptions."""
library = "react-error-boundary"
tag = "ErrorBoundary"
def _render(self, props: dict[str, Any] | None = None) -> Tag:
"""Define how to render the component in React.
Args:
props: The props to render (if None, then use get_props).
Returns:
The tag to render.
"""
# Create the base tag.
tag = Tag(
name=self.tag if not self.alias else self.alias,
special_props=self.special_props,
)
if props is None:
# Add component props to the tag.
props = {
attr[:-1] if attr.endswith("_") else attr: getattr(self, attr)
for attr in self.get_props()
}
# Add ref to element if `id` is not None.
ref = self.get_ref()
if ref is not None:
props["ref"] = Var.create(
ref, _var_is_local=False, _var_is_string=False
)
else:
props = props.copy()
props.update(
**{
trigger: handler
for trigger, handler in self.event_triggers.items()
if trigger not in {EventTriggers.ON_MOUNT, EventTriggers.ON_UNMOUNT}
},
key=self.key,
id=self.id,
class_name=self.class_name,
)
props.update(self._get_style())
props.update(self.custom_attrs)
# remove excluded props from prop dict before adding to tag.
for prop_to_exclude in self._exclude_props():
props.pop(prop_to_exclude, None)
props["onError"] = Var.create_safe(
"logFrontendError", _var_is_string=False, _var_is_local=False
)
props["FallbackComponent"] = Var.create_safe(
"Fallback", _var_is_string=False, _var_is_local=False
)
return tag.add_props(**props)
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.FRONTEND_ERRORS: None} Regarding the errors mentioned by @Lendemor, I think they both occur before the event loop (on which all my logic depends) is instantiated and thus the frontend error events are not propagated to the python side. p.s. Please, check out the latest commit I made just now and let me know what you think. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@maximvlah what about exceptions in reflex.app._process
?
See Line 1162
Do we want to combine exception handlers with telemetry.send_error
? Maybe replace all occurrences of telemetry.send_error
with the backend exception handler and let telemetry.send_error
be part of the default exception handler?
@benedikt-bartscher I must have missed it, just addded backend exception handler to _process, thanks for pointing it out. |
6cd628f introduced a lot of formatting, iirc this has already been done on main. Please rebase/merge main or cleanup this commit |
These changes are not on main, but they shouldn't be because we've added that rule to |
@benedikt-bartscher Done |
There is still too many modified files, the additional blank lines in the docstrings shouldn't be there. |
@Lendemor @benedikt-bartscher ok, it looks like it is clean now, sorry for the mess. I want to rename the branch to a more meaningful name like |
@maximvlah i'm still seeing the blank line changes in many files. you might try just opening a new PR with your renamed and fixed branch and i'll test it out and review it. we really want to get this feature in and appreciate you working on it 😄 |
@masenf ok, will do it now |
All Submissions:
Type of change
New Feature Submission:
Changes To Core Features:
closes #3284