[REF-2158] Enable state when on_load or event_triggers are set. (#2815)

* [REF-2158] Enable state when `on_load` or event_triggers are set.

Basically all events in a reflex app require the backend to be up, even the
built-in server side events round trip to the backend and require "state" even
if no user-defined state classes are declared.

test_app_state_determination: checking that state is enabled at the right time

* Clear out DECORATED_PAGES when initializing a new app
This commit is contained in:
Masen Furer 2024-03-08 17:34:09 -08:00 committed by GitHub
parent bf297e2f5b
commit c809107d09
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 61 additions and 6 deletions

View File

@ -453,14 +453,18 @@ class App(Base):
# Generate the component if it is a callable.
component = self._generate_component(component)
# Ensure state is enabled if this page uses state.
if self.state is None:
for var in component._get_vars(include_children=True):
if not var._var_data:
continue
if not var._var_data.state:
continue
if on_load or component._has_event_triggers():
self.enable_state()
break
else:
for var in component._get_vars(include_children=True):
if not var._var_data:
continue
if not var._var_data.state:
continue
self.enable_state()
break
component = OverlayFragment.create(component)

View File

@ -873,6 +873,20 @@ class Component(BaseComponent, ABC):
return vars
def _has_event_triggers(self) -> bool:
"""Check if the component or children have any event triggers.
Returns:
True if the component or children have any event triggers.
"""
if self.event_triggers:
return True
else:
for child in self.children:
if isinstance(child, Component) and child._has_event_triggers():
return True
return False
def _get_custom_code(self) -> str | None:
"""Get custom code for the component.

View File

@ -225,6 +225,8 @@ class AppHarness:
with chdir(self.app_path):
# ensure config and app are reloaded when testing different app
reflex.config.get_config(reload=True)
# Clean out any `rx.page` decorators from other tests.
reflex.app.DECORATED_PAGES.clear()
# reset rx.State subclasses
State.class_subclasses.clear()
State.class_subclasses.update(INTERNAL_STATES)

View File

@ -1319,3 +1319,38 @@ def test_app_wrap_priority(compilable_app):
")"
"}"
) in "".join(app_js_lines)
def test_app_state_determination():
"""Test that the stateless status of an app is determined correctly."""
a1 = App()
assert a1.state is None
# No state, no router, no event handlers.
a1.add_page(rx.box("Index"), route="/")
assert a1.state is None
# Add a page with `on_load` enables state.
a1.add_page(rx.box("About"), route="/about", on_load=rx.console_log(""))
assert a1.state is not None
a2 = App()
assert a2.state is None
# Referencing a state Var enables state.
a2.add_page(rx.box(rx.text(GenState.value)), route="/")
assert a2.state is not None
a3 = App()
assert a3.state is None
# Referencing router enables state.
a3.add_page(rx.box(rx.text(State.router.page.full_path)), route="/")
assert a3.state is not None
a4 = App()
assert a4.state is None
# Referencing an event handler enables state.
a4.add_page(rx.box(rx.button("Click", on_click=rx.console_log(""))), route="/")
assert a4.state is not None