Skip to content

Commit 4ed8ed2

Browse files
authored
ENG-8113: handle_frontend_exception triggers auto reload (#5922)
* ENG-8113: handle_frontend_exception triggers auto reload For configurable types of errors refresh once per configurable period in an attempt to automatically clear spurious issues when reloading. * Address feedback comments * move cooldown time to env var
1 parent e53b65d commit 4ed8ed2

File tree

2 files changed

+36
-2
lines changed

2 files changed

+36
-2
lines changed

reflex/environment.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,9 @@ class EnvironmentVariables:
696696
# How long to delay writing updated states to disk. (Higher values mean less writes, but more chance of lost data.)
697697
REFLEX_STATE_MANAGER_DISK_DEBOUNCE_SECONDS: EnvVar[float] = env_var(2.0)
698698

699+
# How long to wait between automatic reload on frontend error to avoid reload loops.
700+
REFLEX_AUTO_RELOAD_COOLDOWN_TIME_MS: EnvVar[int] = env_var(10_000)
701+
699702

700703
environment = EnvironmentVariables()
701704

reflex/state.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@
1111
import functools
1212
import inspect
1313
import pickle
14+
import re
1415
import sys
1516
import time
1617
import typing
1718
import warnings
18-
from collections.abc import AsyncIterator, Callable, Sequence
19+
from collections.abc import AsyncIterator, Callable, Iterator, Sequence
1920
from enum import Enum
2021
from hashlib import md5
2122
from importlib.util import find_spec
@@ -34,6 +35,7 @@
3435
Event,
3536
EventHandler,
3637
EventSpec,
38+
call_script,
3739
fix_events,
3840
)
3941
from reflex.istate import HANDLED_PICKLE_ERRORS, debug_failed_pickles
@@ -2470,11 +2472,26 @@ def wrapper() -> Component:
24702472
return wrapper
24712473

24722474

2475+
# sessionStorage key holding the ms timestamp of the last reload on error
2476+
LAST_RELOADED_KEY = "reflex_last_reloaded_on_error"
2477+
2478+
24732479
class FrontendEventExceptionState(State):
24742480
"""Substate for handling frontend exceptions."""
24752481

2482+
# If the frontend error message contains any of these strings, automatically reload the page.
2483+
auto_reload_on_errors: ClassVar[list[re.Pattern]] = [
2484+
re.compile( # Chrome/Edge
2485+
re.escape("TypeError: Cannot read properties of null")
2486+
),
2487+
re.compile(re.escape("TypeError: null is not an object")), # Safari
2488+
re.compile(r"TypeError: can't access property \".*\" of null"), # Firefox
2489+
]
2490+
24762491
@event
2477-
def handle_frontend_exception(self, info: str, component_stack: str) -> None:
2492+
def handle_frontend_exception(
2493+
self, info: str, component_stack: str
2494+
) -> Iterator[EventSpec]:
24782495
"""Handle frontend exceptions.
24792496
24802497
If a frontend exception handler is provided, it will be called.
@@ -2484,7 +2501,21 @@ def handle_frontend_exception(self, info: str, component_stack: str) -> None:
24842501
info: The exception information.
24852502
component_stack: The stack trace of the component where the exception occurred.
24862503
2504+
Yields:
2505+
Optional auto-reload event for certain errors outside cooldown period.
24872506
"""
2507+
# Handle automatic reload for certain errors.
2508+
if type(self).auto_reload_on_errors and any(
2509+
error.search(info) for error in type(self).auto_reload_on_errors
2510+
):
2511+
yield call_script(
2512+
f"const last_reload = parseInt(window.sessionStorage.getItem('{LAST_RELOADED_KEY}')) || 0;"
2513+
f"if (Date.now() - last_reload > {environment.REFLEX_AUTO_RELOAD_COOLDOWN_TIME_MS.get()})"
2514+
"{"
2515+
f"window.sessionStorage.setItem('{LAST_RELOADED_KEY}', Date.now().toString());"
2516+
"window.location.reload();"
2517+
"}"
2518+
)
24882519
prerequisites.get_and_validate_app().app.frontend_exception_handler(
24892520
Exception(info)
24902521
)

0 commit comments

Comments
 (0)