From e085dcab49c73896b2511f327c566cceb8d15c1c Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Fri, 8 Mar 2024 17:34:09 -0800 Subject: [PATCH] [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 --- reflex/app.py | 16 ++++++++++------ reflex/components/component.py | 14 ++++++++++++++ reflex/testing.py | 2 ++ tests/test_app.py | 35 ++++++++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 6 deletions(-) diff --git a/reflex/app.py b/reflex/app.py index d2b9d868e..783125415 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -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) diff --git a/reflex/components/component.py b/reflex/components/component.py index 250cc1894..44e1ffcdc 100644 --- a/reflex/components/component.py +++ b/reflex/components/component.py @@ -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. diff --git a/reflex/testing.py b/reflex/testing.py index 9a8f40f54..ae73af84c 100644 --- a/reflex/testing.py +++ b/reflex/testing.py @@ -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) diff --git a/tests/test_app.py b/tests/test_app.py index 0838d4a90..f0fdd8031 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -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