diff --git a/news/6923.bugfix.md b/news/6923.bugfix.md new file mode 100644 index 00000000000..e39a943c27d --- /dev/null +++ b/news/6923.bugfix.md @@ -0,0 +1 @@ +Allow State Vars for page titles and descriptions in `@rx.page` and compiled metadata. diff --git a/reflex/app.py b/reflex/app.py index 63ec53a4c75..56d01bc8204 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -1020,8 +1020,12 @@ def _prepare_page( from reflex_components_core.el.elements import span component = span("404: Page not found") - title = title or constants.Page404.TITLE - description = description or constants.Page404.DESCRIPTION + title = title if title is not None else constants.Page404.TITLE + description = ( + description + if description is not None + else constants.Page404.DESCRIPTION + ) image = image or constants.Page404.IMAGE else: if component is None: diff --git a/reflex/compiler/utils.py b/reflex/compiler/utils.py index 216b5d4bed6..4664cd8200d 100644 --- a/reflex/compiler/utils.py +++ b/reflex/compiler/utils.py @@ -784,10 +784,10 @@ def get_memo_module_path(segments: tuple[str, ...]) -> str: def add_meta( page: Component, - title: str, + title: str | Var, image: str, meta: Sequence[Mapping[str, Any] | Component], - description: str | None = None, + description: str | Var | None = None, ) -> Component: """Add metadata to a page. @@ -806,7 +806,7 @@ def add_meta( ] children: list[Any] = [Title.create(title)] - if description: + if description is not None: children.append(Description.create(content=description)) children.append(Image.create(content=image)) diff --git a/reflex/page.py b/reflex/page.py index 5b636bc0598..ed6abc9b33c 100644 --- a/reflex/page.py +++ b/reflex/page.py @@ -12,13 +12,14 @@ from typing import Any from reflex_base.event import EventType + from reflex_base.vars import Var def page( route: str | None = None, - title: str | None = None, + title: str | Var | None = None, image: str | None = None, - description: str | None = None, + description: str | Var | None = None, meta: list[Any] | None = None, script_tags: list[Any] | None = None, on_load: EventType[()] | None = None, @@ -49,11 +50,11 @@ def decorator(render_fn: Callable): kwargs: dict[str, Any] = {} if route: kwargs["route"] = route - if title: + if title is not None: kwargs["title"] = title if image: kwargs["image"] = image - if description: + if description is not None: kwargs["description"] = description if meta: kwargs["meta"] = meta @@ -75,9 +76,9 @@ class PageNamespace: def __new__( cls, route: str | None = None, - title: str | None = None, + title: str | Var | None = None, image: str | None = None, - description: str | None = None, + description: str | Var | None = None, meta: list[Any] | None = None, script_tags: list[Any] | None = None, on_load: EventType[()] | None = None, diff --git a/tests/units/compiler/test_compiler.py b/tests/units/compiler/test_compiler.py index b467b0823c7..04297dbeef0 100644 --- a/tests/units/compiler/test_compiler.py +++ b/tests/units/compiler/test_compiler.py @@ -19,7 +19,7 @@ from reflex_base.utils.imports import ImportVar, ParsedImportDict from reflex_base.vars.base import Var from reflex_base.vars.sequence import LiteralStringVar -from reflex_components_core.base import document +from reflex_components_core.base import Description, document from reflex_components_core.base.document import Links, Scripts from reflex_components_core.el.elements.metadata import Head, Link, Meta from reflex_components_core.el.elements.other import Html @@ -617,6 +617,25 @@ def test_create_document_root(): assert isinstance(root.children[0].children[5], Links) +def test_add_meta_accepts_dynamic_description(): + """Dynamic page descriptions should be represented as component Vars.""" + + class PageState(rx.State): + description: str = "Dynamic description" + + page = rx.box() + utils.add_meta( + page, + title="title", + image="", + meta=(), + description=PageState.description, + ) + + assert isinstance(page.children[1], Description) + assert page.children[1].content is PageState.description + + def test_create_document_root_with_scripts(): # Test with components. comps = [ diff --git a/tests/units/test_app.py b/tests/units/test_app.py index 484796337a5..6c62999eb51 100644 --- a/tests/units/test_app.py +++ b/tests/units/test_app.py @@ -261,6 +261,24 @@ def test_add_page_default_route( assert app._pages.keys() == {"index", "about"} +def test_prepare_404_page_preserves_dynamic_metadata(): + """404 fallback defaults should not evaluate explicitly supplied Vars.""" + + class PageState(rx.State): + title: str = "Dynamic title" + description: str = "Dynamic description" + + app = App() + prepared = app._prepare_page( + route=constants.Page404.SLUG, + title=PageState.title, + description=PageState.description, + ) + + assert prepared.page.title is PageState.title + assert prepared.page.description is PageState.description + + def test_add_page_set_route(app: App, index_page: ComponentCallable): """Test adding a page to an app. diff --git a/tests/units/test_page.py b/tests/units/test_page.py index 61ba5c886e0..b7dee9d8705 100644 --- a/tests/units/test_page.py +++ b/tests/units/test_page.py @@ -1,6 +1,6 @@ from reflex_base.registry import RegistrationContext -from reflex import text +from reflex import State, text from reflex.page import page @@ -22,6 +22,27 @@ def foo_(): assert page_data == {} +def test_page_decorator_accepts_state_metadata( + clean_registration_context: RegistrationContext, +): + """State Vars should be preserved as page metadata without bool evaluation.""" + + class PageState(State): + title: str = "Dynamic title" + description: str = "Dynamic description" + + def foo_(): + return text("foo") + + assert clean_registration_context.decorated_pages == [] + page(title=PageState.title, description=PageState.description)(foo_) + + assert len(clean_registration_context.decorated_pages) == 1 + _, page_data = clean_registration_context.decorated_pages[0] + assert page_data["title"] is PageState.title + assert page_data["description"] is PageState.description + + def test_page_decorator_with_kwargs( clean_registration_context: RegistrationContext, ):