From 062b767995e7bf7021dbcad6e31468338ebfd96a Mon Sep 17 00:00:00 2001 From: Harsh Thakare Date: Sat, 22 Aug 2026 00:07:41 +0530 Subject: [PATCH] Allow Vars in page metadata --- news/6923.bugfix.md | 1 + reflex/app.py | 8 ++++++-- reflex/compiler/utils.py | 6 +++--- reflex/page.py | 13 +++++++------ tests/units/compiler/test_compiler.py | 21 ++++++++++++++++++++- tests/units/test_app.py | 18 ++++++++++++++++++ tests/units/test_page.py | 22 +++++++++++++++++++++- 7 files changed, 76 insertions(+), 13 deletions(-) create mode 100644 news/6923.bugfix.md 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 3ef9313c7b1..269f0ce4b36 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -1016,8 +1016,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 4501a1ebc81..8e99a1bb4b9 100644 --- a/reflex/compiler/utils.py +++ b/reflex/compiler/utils.py @@ -787,10 +787,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. @@ -809,7 +809,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 2e3f8fc613e..0705f49717a 100644 --- a/reflex/page.py +++ b/reflex/page.py @@ -11,15 +11,16 @@ from typing import Any from reflex_base.event import EventType + from reflex_base.vars import Var DECORATED_PAGES: dict[str, list[tuple[Callable, dict[str, Any]]]] = defaultdict(list) 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, @@ -51,11 +52,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 @@ -79,9 +80,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 bd97f3b9e71..c21d451e223 100644 --- a/tests/units/compiler/test_compiler.py +++ b/tests/units/compiler/test_compiler.py @@ -18,7 +18,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 @@ -616,6 +616,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 c50af0271db..112e3e4ae10 100644 --- a/tests/units/test_app.py +++ b/tests/units/test_app.py @@ -260,6 +260,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 07cb5c4e2a4..25ce59fa9f1 100644 --- a/tests/units/test_page.py +++ b/tests/units/test_page.py @@ -1,6 +1,6 @@ from reflex_base.config import get_config -from reflex import text +from reflex import State, text from reflex.page import DECORATED_PAGES, page @@ -18,6 +18,26 @@ def foo_(): DECORATED_PAGES.clear() +def test_page_decorator_accepts_state_metadata(): + """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") + + DECORATED_PAGES.clear() + page(title=PageState.title, description=PageState.description)(foo_) + + page_data = DECORATED_PAGES.get(get_config().app_name, [])[0][1] + assert page_data["title"] is PageState.title + assert page_data["description"] is PageState.description + + DECORATED_PAGES.clear() + + def test_page_decorator_with_kwargs(): def foo_(): return text("foo")