From 5e22cb2191b0e30a0baa4ef78a68beb3a0dc887c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 19 May 2026 00:15:56 +0000 Subject: [PATCH 1/2] Migrate docs sidebar_comp to @rx._x.memo Convert sidebar_comp into an experimental memo component so the rendered sidebar tree compiles to a single React component shared across every docpage. The route-specific url and per-section indices are now passed as runtime props, and the per-page JSX no longer re-inlines the entire sidebar. - Decorate sidebar_comp with @rx._x.memo; type all parameters as rx.Var subclasses (StringVar / ArrayVar) so the memo system can build placeholders and emit JSX with Var operations. - Update helpers (sidebar_leaf_guide, sidebar_leaf, sidebar_item_comp, sidebar_category, create_sidebar_section) to accept Var-typed url and index parameters. - Replace Python conditionals on Var-derived expressions with rx.cond. - Convert index handling (bool(index), index[1:] if open else []) to Var operations (index.length(), rx.cond(is_open, index[1:], []).to(...)). - Express startswith / contains URL checks via StringVar.startswith / StringVar.contains chained with Var __or__ / __and__ / __invert__. - Drop the unused cli_ref_index and width parameters from sidebar_comp; sidebar() still accepts width for callers but no longer threads it through. normalize_url stays in the outer sidebar() since it relies on Python string ops not expressible as Var operations. https://claude.ai/code/session_013mppZsm9QZVz6DTPYokXR2 --- .../templates/docpage/sidebar/sidebar.py | 713 +++++++++--------- 1 file changed, 360 insertions(+), 353 deletions(-) diff --git a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py index c6fe09c239a..aea637157ba 100644 --- a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py +++ b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py @@ -19,7 +19,7 @@ enterprise_items, enterprise_usage_items, ) -from .sidebar_items.learn import backend, cli_ref, frontend, hosting, learn +from .sidebar_items.learn import backend, frontend, hosting, learn from .sidebar_items.recipes import recipes from .sidebar_items.reference import api_reference from .state import SideBarBase, SideBarItem @@ -106,35 +106,34 @@ def sidebar_link(*children, **props): ) -def sidebar_leaf_guide(is_active: bool) -> rx.Component: +def sidebar_leaf_guide(is_active: rx.vars.BooleanVar) -> rx.Component: """Render the active sidebar leaf guide segment.""" - return ( + return rx.cond( + is_active, rx.el.div( class_name="absolute left-0 -top-1 -bottom-1 w-px bg-primary-10 pointer-events-none", - ) - if is_active - else rx.fragment() + ), + rx.fragment(), ) def sidebar_leaf( - item_index: str, item: SideBarItem, - url: str, + url: rx.vars.StringVar[str], guide_margin_class: str = "ml-[3rem]", ) -> rx.Component: """Get the leaf node of the sidebar.""" item.link = item.link.replace("_", "-").rstrip("/") + "/" - is_active = item.link == url + is_active = url == item.link if item.outer: return rx.el.li( sidebar_link( rx.flex( rx.text( item.names, - color=c_color("violet", 9) - if is_active - else c_color("slate", 9), + color=rx.cond( + is_active, c_color("violet", 9), c_color("slate", 9) + ), _hover={ "color": c_color("slate", 11), }, @@ -149,39 +148,36 @@ def sidebar_leaf( class_name="m-0 p-0 !overflow-visible w-full list-none", ) - active_background = ( - [ - rx.el.div( - class_name="absolute left-0 top-1/2 -translate-y-1/2 w-full h-8 rounded-lg bg-m-slate-2 dark:bg-slate-3 z-[-1]", - ) - ] - if is_active - else [] - ) return rx.el.li( sidebar_link( - *active_background, + rx.cond( + is_active, + rx.el.div( + class_name="absolute left-0 top-1/2 -translate-y-1/2 w-full h-8 rounded-lg bg-m-slate-2 dark:bg-slate-3 z-[-1]", + ), + rx.fragment(), + ), rx.flex( sidebar_leaf_guide(is_active), rx.text( item.names, - class_name=( - "m-0 text-sm text-primary-10 font-[525] transition-color pl-4" - if is_active - else "m-0 text-sm text-secondary-11 hover:text-secondary-12 transition-color w-full font-[525]" + class_name=rx.cond( + is_active, + "m-0 text-sm text-primary-10 font-[525] transition-color pl-4", + "m-0 text-sm text-secondary-11 hover:text-secondary-12 transition-color w-full font-[525]", ), ), - class_name=( - f"relative {guide_margin_class} max-w-[14rem] h-8 flex items-center" - if is_active - else "relative pl-4 h-8 flex items-center" + class_name=rx.cond( + is_active, + f"relative {guide_margin_class} max-w-[14rem] h-8 flex items-center", + "relative pl-4 h-8 flex items-center", ), ), href=item.link, - class_name=( - "block w-full relative" - if is_active - else f"block w-full {guide_margin_class}" + class_name=rx.cond( + is_active, + "block w-full relative", + f"block w-full {guide_margin_class}", ), ), class_name="m-0 p-0 !overflow-visible w-full relative list-none", @@ -189,22 +185,22 @@ def sidebar_leaf( def sidebar_item_comp( - item_index: str, + item_index: int, item: SideBarItem, - index: list[int], - url: str, + index: rx.vars.ArrayVar[list[int]], + url: rx.vars.StringVar[str], guide_margin_class: str = "ml-[3rem]", -): +) -> rx.Component: + """Render an item in the sidebar, recursing into its children.""" if not item.children: return sidebar_leaf( - item_index=item_index, item=item, url=url, guide_margin_class=guide_margin_class, ) - is_open = bool(index) and item_index == f"index{index[0]}" - nested_index = index[1:] if is_open else [] + is_open = (index.length() > 0) & (index[0] == item_index) + nested_index = rx.cond(is_open, index[1:], []).to(list[int]) child_guide_left_class = ( "left-[3rem]" if has_sidebar_icon(item.names) else "left-[2.5rem]" ) @@ -232,7 +228,7 @@ def sidebar_item_comp( ), *[ sidebar_item_comp( - item_index="index" + str(child_index), + item_index=child_index, item=child, index=nested_index, url=url, @@ -337,19 +333,22 @@ def filter_out_non_sidebar_items(items: list[SideBarBase]) -> list[SideBarItem]: return [item for item in items if isinstance(item, SideBarItem)] -def sidebar_category(name: str, url: str, icon: str, active: bool): - active_background = ( - [ - rx.el.div( - class_name="absolute left-0 top-1/2 -translate-y-1/2 w-full h-8 rounded-lg bg-m-slate-2 dark:bg-slate-3 z-[-1]", - ) - ] - if active - else [] - ) +def sidebar_category( + name: str, + url: str, + icon: str, + active: rx.Var[bool] | bool, +) -> rx.Component: + """Render a top-level sidebar category entry.""" return rx.el.li( rx.link( - *active_background, + rx.cond( + active, + rx.el.div( + class_name="absolute left-0 top-1/2 -translate-y-1/2 w-full h-8 rounded-lg bg-m-slate-2 dark:bg-slate-3 z-[-1]", + ), + rx.fragment(), + ), rx.box( rx.icon( tag=icon, @@ -361,7 +360,7 @@ def sidebar_category(name: str, url: str, icon: str, active: bool): ), class_name=ui.cn( "cursor-pointer flex flex-row justify-start items-center gap-2.5 ml-[3rem] text-sm text-secondary-11 hover:text-secondary-12 h-8", - "text-slate-12" if active else "", + rx.cond(active, "text-slate-12", ""), ), ), href=url, @@ -377,10 +376,11 @@ def create_sidebar_section( section_title: str, section_url: str, items: list[SideBarItem], - index: list[int], - url: str, + index: rx.vars.ArrayVar[list[int]], + url: rx.vars.StringVar[str], connected_line: bool = False, ) -> rx.Component: + """Render a titled section of the sidebar.""" return rx.el.li( rx.link( rx.el.h2( @@ -394,7 +394,7 @@ def create_sidebar_section( rx.el.ul( *[ sidebar_item_comp( - item_index="index" + str(item_index), + item_index=item_index, item=item, index=index, url=url, @@ -427,28 +427,33 @@ def normalize_url(url: str | None) -> str: return path.rstrip("/") + "/" +@rx._x.memo def sidebar_comp( - url: str, - learn_index: list[int], - component_lib_index: list[int], - frontend_index: list[int], - backend_index: list[int], - hosting_index: list[int], - html_lib_index: list[int], - graphing_libs_index: list[int], - api_reference_index: list[int], - recipes_index: list[int], - enterprise_usage_index: list[int], - enterprise_component_index: list[int], - ai_onboarding_index: list[int], - mcp_index: list[int], - skills_index: list[int], - # - cli_ref_index: list[int], - ai_builder_overview_index: list[int], - ai_builder_integrations_index: list[int], - width: str = "100%", -): + url: rx.vars.StringVar[str], + learn_index: rx.vars.ArrayVar[list[int]], + component_lib_index: rx.vars.ArrayVar[list[int]], + frontend_index: rx.vars.ArrayVar[list[int]], + backend_index: rx.vars.ArrayVar[list[int]], + hosting_index: rx.vars.ArrayVar[list[int]], + html_lib_index: rx.vars.ArrayVar[list[int]], + graphing_libs_index: rx.vars.ArrayVar[list[int]], + api_reference_index: rx.vars.ArrayVar[list[int]], + recipes_index: rx.vars.ArrayVar[list[int]], + enterprise_usage_index: rx.vars.ArrayVar[list[int]], + enterprise_component_index: rx.vars.ArrayVar[list[int]], + ai_onboarding_index: rx.vars.ArrayVar[list[int]], + mcp_index: rx.vars.ArrayVar[list[int]], + skills_index: rx.vars.ArrayVar[list[int]], + ai_builder_overview_index: rx.vars.ArrayVar[list[int]], + ai_builder_integrations_index: rx.vars.ArrayVar[list[int]], +) -> rx.Component: + """Render the docs sidebar. + + The function is decorated with ``rx._x.memo`` so the rendered tree compiles + to a single React component that receives the runtime ``url`` and per-section + indices as props. ``url`` is expected to be pre-normalized by the caller + (see ``sidebar`` below). + """ from reflex_docs.pages.docs import ai_builder as ai_builder_pages from reflex_docs.pages.docs import enterprise, getting_started, state, ui from reflex_docs.pages.docs import hosting as hosting_page @@ -457,241 +462,257 @@ def sidebar_comp( from reflex_docs.pages.docs.library import library from reflex_docs.pages.docs.recipes_overview import overview - path = normalize_url(url) - is_docs_hosting = path.startswith("/hosting/") - is_docs_ai_builder = path.startswith("/ai/") - is_ai_mcp_or_skills = path.startswith(( - "/ai/integrations/ai-onboarding/", - "/ai/integrations/skills/", - "/ai/integrations/agents-md/", - "/ai/integrations/mcp", - )) - - ai_category = 1 if is_ai_mcp_or_skills else 0 - docs_category = 0 - if "library" in path or "/mcp-" in path: - docs_category = 1 - elif "api-reference" in path: - docs_category = 2 - elif "enterprise" in path: - docs_category = 3 - - if is_docs_hosting: - categories = rx.el.ul( - sidebar_category( - "Cloud", - hosting_page.deploy_quick_start.path, - "cloud", - True, - ), - class_name="flex flex-col items-start gap-2 w-full list-none", - ) - content = rx.el.ul( - create_sidebar_section( - "Cloud", - hosting_page.deploy_quick_start.path, - hosting, - hosting_index, - url, - ), - class_name="m-0 p-0 flex flex-col items-start gap-8 w-full list-none list-style-none", - ) - elif is_docs_ai_builder: - categories = rx.el.ul( - sidebar_category( - "AI Builder", - ai_builder_pages.overview.best_practices.path, - "bot", - ai_category == 0, - ), - sidebar_category( - "MCP/Skills", - ai_builder_pages.integrations.ai_onboarding.path, - "plug", - ai_category == 1, - ), - class_name="flex flex-col items-start gap-2 w-full list-none", - ) - content = ( - rx.el.ul( - create_sidebar_section( - "Overview", - ai_builder_pages.integrations.ai_onboarding.path, - ai_onboarding_items, - ai_onboarding_index, - url, - ), - create_sidebar_section( - "MCP", - ai_builder_pages.integrations.mcp_overview.path, - mcp_items, - mcp_index, - url, - connected_line=True, - ), - create_sidebar_section( - "Skills", - ai_builder_pages.integrations.skills.path, - skills_items, - skills_index, - url, - connected_line=True, - ), - class_name="m-0 p-0 flex flex-col items-start gap-8 w-full list-none list-style-none", - ) - if ai_category == 1 - else rx.el.ul( - create_sidebar_section( - "Overview", - ai_builder_pages.overview.best_practices.path, - ai_builder_overview_items, - ai_builder_overview_index, - url, + is_docs_hosting = url.startswith("/hosting/") + is_docs_ai_builder = url.startswith("/ai/") + is_ai_mcp_or_skills = ( + url.startswith("/ai/integrations/ai-onboarding/") + | url.startswith("/ai/integrations/skills/") + | url.startswith("/ai/integrations/agents-md/") + | url.startswith("/ai/integrations/mcp") + ) + + is_library = url.contains("library") | url.contains("/mcp-") + is_api_reference = url.contains("api-reference") + is_enterprise = url.contains("enterprise") + is_default_docs = ~is_library & ~is_api_reference & ~is_enterprise + + hosting_categories = rx.el.ul( + sidebar_category( + "Cloud", + hosting_page.deploy_quick_start.path, + "cloud", + True, + ), + class_name="flex flex-col items-start gap-2 w-full list-none", + ) + hosting_content = rx.el.ul( + create_sidebar_section( + "Cloud", + hosting_page.deploy_quick_start.path, + hosting, + hosting_index, + url, + ), + class_name="m-0 p-0 flex flex-col items-start gap-8 w-full list-none list-style-none", + ) + + ai_builder_categories = rx.el.ul( + sidebar_category( + "AI Builder", + ai_builder_pages.overview.best_practices.path, + "bot", + ~is_ai_mcp_or_skills, + ), + sidebar_category( + "MCP/Skills", + ai_builder_pages.integrations.ai_onboarding.path, + "plug", + is_ai_mcp_or_skills, + ), + class_name="flex flex-col items-start gap-2 w-full list-none", + ) + ai_mcp_skills_content = rx.el.ul( + create_sidebar_section( + "Overview", + ai_builder_pages.integrations.ai_onboarding.path, + ai_onboarding_items, + ai_onboarding_index, + url, + ), + create_sidebar_section( + "MCP", + ai_builder_pages.integrations.mcp_overview.path, + mcp_items, + mcp_index, + url, + connected_line=True, + ), + create_sidebar_section( + "Skills", + ai_builder_pages.integrations.skills.path, + skills_items, + skills_index, + url, + connected_line=True, + ), + class_name="m-0 p-0 flex flex-col items-start gap-8 w-full list-none list-style-none", + ) + ai_builder_overview_content = rx.el.ul( + create_sidebar_section( + "Overview", + ai_builder_pages.overview.best_practices.path, + ai_builder_overview_items, + ai_builder_overview_index, + url, + ), + create_sidebar_section( + "Integrations", + ai_builder_pages.integrations.overview.path, + ai_builder_integrations, + ai_builder_integrations_index, + url, + ), + class_name="m-0 p-0 flex flex-col items-start gap-8 w-full list-none list-style-none", + ) + ai_builder_content = rx.cond( + is_ai_mcp_or_skills, + ai_mcp_skills_content, + ai_builder_overview_content, + ) + + docs_categories = rx.el.ul( + sidebar_category( + "Learn", + getting_started.introduction.path, + "graduation-cap", + is_default_docs, + ), + sidebar_category( + "Components", + library.path, + "layout-panel-left", + is_library, + ), + sidebar_category( + "API Reference", + pages[0].path, + "book-text", + ~is_library & is_api_reference, + ), + sidebar_category( + "Enterprise", + enterprise.overview.path, + "building-2", + ~is_library & ~is_api_reference & is_enterprise, + ), + class_name="flex flex-col items-start gap-2 w-full list-none", + ) + library_content = rx.el.ul( + create_sidebar_section( + "Core", + library.path, + component_lib, + component_lib_index, + url, + ), + create_sidebar_section( + "Graphing", + library.path, + graphing_libs, + graphing_libs_index, + url, + ), + create_sidebar_section( + "Other", + "/library/html/", + html_lib, + html_lib_index, + url, + ), + rx.link( # pyright: ignore [reportCallIssue] + rx.box( # pyright: ignore [reportCallIssue] + rx.box( # pyright: ignore [reportCallIssue] + rx.icon("atom", size=16), # pyright: ignore [reportCallIssue] + rx.el.h5( + "Custom Components", + class_name="font-smbold text-[0.875rem] text-slate-12 leading-5 tracking-[-0.01313rem] transition-color", + ), + class_name="flex flex-row items-center gap-3 text-slate-12", ), - create_sidebar_section( - "Integrations", - ai_builder_pages.integrations.overview.path, - ai_builder_integrations, - ai_builder_integrations_index, - url, + rx.text( # pyright: ignore [reportCallIssue] + "See what components people have made with Reflex!", + class_name="font-small text-slate-9", ), - class_name="m-0 p-0 flex flex-col items-start gap-8 w-full list-none list-style-none", - ) - ) - else: - categories = rx.el.ul( - sidebar_category( - "Learn", - getting_started.introduction.path, - "graduation-cap", - docs_category == 0, + class_name="flex flex-col gap-2 border-slate-5 bg-slate-1 hover:bg-slate-3 shadow-large px-3.5 py-2 border rounded-xl transition-bg", ), - sidebar_category( - "Components", - library.path, - "layout-panel-left", - docs_category == 1, - ), - sidebar_category( - "API Reference", - pages[0].path, - "book-text", - docs_category == 2, - ), - sidebar_category( - "Enterprise", - enterprise.overview.path, - "building-2", - docs_category == 3, + underline="none", + href=custom_components.path, + class_name="w-fit lg:ml-[2.5rem]", + ), + class_name="m-0 p-0 flex flex-col items-start gap-8 w-full list-none list-style-none", + ) + api_reference_content = rx.el.ul( + create_sidebar_section( + "Reference", + pages[0].path, + api_reference, + api_reference_index, + url, + ), + class_name="m-0 p-0 flex flex-col items-start gap-8 w-full list-none list-style-none", + ) + enterprise_content = rx.el.ul( + create_sidebar_section( + "Enterprise Usage", + enterprise.overview.path, + enterprise_usage_items, + enterprise_usage_index, + url, + ), + create_sidebar_section( + "Components", + enterprise.components.path, + enterprise_component_items, + enterprise_component_index, + url, + ), + class_name="m-0 p-0 flex flex-col items-start gap-8 w-full list-none list-style-none", + ) + default_docs_content = rx.el.ul( + create_sidebar_section( + "Onboarding", + getting_started.introduction.path, + learn, + learn_index, + url, + ), + create_sidebar_section( + "User Interface", + ui.overview.path, + filter_out_non_sidebar_items(frontend), + frontend_index, + url, + ), + create_sidebar_section( + "State", + state.overview.path, + filter_out_non_sidebar_items(backend), + backend_index, + url, + ), + create_sidebar_section( + "Recipes", + overview.path, + recipes, + recipes_index, + url, + ), + class_name="m-0 p-0 flex flex-col items-start gap-8 w-full list-none list-style-none", + ) + docs_content = rx.cond( + is_library, + library_content, + rx.cond( + is_api_reference, + api_reference_content, + rx.cond( + is_enterprise, + enterprise_content, + default_docs_content, ), - class_name="flex flex-col items-start gap-2 w-full list-none", - ) - if docs_category == 1: - content = rx.el.ul( - create_sidebar_section( - "Core", - library.path, - component_lib, - component_lib_index, - url, - ), - create_sidebar_section( - "Graphing", - library.path, - graphing_libs, - graphing_libs_index, - url, - ), - create_sidebar_section( - "Other", - "/library/html/", - html_lib, - html_lib_index, - url, - ), - rx.link( # pyright: ignore [reportCallIssue] - rx.box( # pyright: ignore [reportCallIssue] - rx.box( # pyright: ignore [reportCallIssue] - rx.icon("atom", size=16), # pyright: ignore [reportCallIssue] - rx.el.h5( - "Custom Components", - class_name="font-smbold text-[0.875rem] text-slate-12 leading-5 tracking-[-0.01313rem] transition-color", - ), - class_name="flex flex-row items-center gap-3 text-slate-12", - ), - rx.text( # pyright: ignore [reportCallIssue] - "See what components people have made with Reflex!", - class_name="font-small text-slate-9", - ), - class_name="flex flex-col gap-2 border-slate-5 bg-slate-1 hover:bg-slate-3 shadow-large px-3.5 py-2 border rounded-xl transition-bg", - ), - underline="none", - href=custom_components.path, - class_name="w-fit lg:ml-[2.5rem]", - ), - class_name="m-0 p-0 flex flex-col items-start gap-8 w-full list-none list-style-none", - ) - elif docs_category == 2: - content = rx.el.ul( - create_sidebar_section( - "Reference", - pages[0].path, - api_reference, - api_reference_index, - url, - ), - class_name="m-0 p-0 flex flex-col items-start gap-8 w-full list-none list-style-none", - ) - elif docs_category == 3: - content = rx.el.ul( - create_sidebar_section( - "Enterprise Usage", - enterprise.overview.path, - enterprise_usage_items, - enterprise_usage_index, - url, - ), - create_sidebar_section( - "Components", - enterprise.components.path, - enterprise_component_items, - enterprise_component_index, - url, - ), - class_name="m-0 p-0 flex flex-col items-start gap-8 w-full list-none list-style-none", - ) - else: - content = rx.el.ul( - create_sidebar_section( - "Onboarding", - getting_started.introduction.path, - learn, - learn_index, - url, - ), - create_sidebar_section( - "User Interface", - ui.overview.path, - filter_out_non_sidebar_items(frontend), - frontend_index, - url, - ), - create_sidebar_section( - "State", - state.overview.path, - filter_out_non_sidebar_items(backend), - backend_index, - url, - ), - create_sidebar_section( - "Recipes", - overview.path, - recipes, - recipes_index, - url, - ), - class_name="m-0 p-0 flex flex-col items-start gap-8 w-full list-none list-style-none", - ) + ), + ) + + categories = rx.cond( + is_docs_hosting, + hosting_categories, + rx.cond(is_docs_ai_builder, ai_builder_categories, docs_categories), + ) + content = rx.cond( + is_docs_hosting, + hosting_content, + rx.cond(is_docs_ai_builder, ai_builder_content, docs_content), + ) return rx.box( # pyright: ignore [reportCallIssue] categories, @@ -709,54 +730,40 @@ def sidebar_comp( def sidebar(url=None, width: str = "100%") -> rx.Component: - """Render the sidebar.""" - normalized_url = normalize_url(url) - learn_index = calculate_index(learn, normalized_url) - component_lib_index = calculate_index(component_lib, normalized_url) - frontend_index = calculate_index(frontend, normalized_url) - backend_index = calculate_index(backend, normalized_url) - hosting_index = calculate_index(hosting, normalized_url) - html_lib_index = calculate_index(html_lib, normalized_url) - graphing_libs_index = calculate_index(graphing_libs, normalized_url) - api_reference_index = calculate_index(api_reference, normalized_url) - recipes_index = calculate_index(recipes, normalized_url) - enterprise_usage_index = calculate_index(enterprise_usage_items, normalized_url) - enterprise_component_index = calculate_index( - enterprise_component_items, normalized_url - ) - - cli_ref_index = calculate_index(cli_ref, normalized_url) - ai_builder_overview_index = calculate_index( - ai_builder_overview_items, normalized_url - ) - ai_builder_integrations_index = calculate_index( - ai_builder_integrations, normalized_url - ) - ai_onboarding_index = calculate_index(ai_onboarding_items, normalized_url) - mcp_index = calculate_index(mcp_items, normalized_url) - skills_index = calculate_index(skills_items, normalized_url) + """Render the sidebar. + ``sidebar`` stays as a regular function (rather than a memo) because its + body invokes Python-only helpers like ``normalize_url`` and + ``calculate_index`` that aren't expressible as Var operations. + """ + normalized_url = normalize_url(url) return rx.box( sidebar_comp( url=normalized_url, - learn_index=learn_index, - component_lib_index=component_lib_index, - frontend_index=frontend_index, - backend_index=backend_index, - hosting_index=hosting_index, - html_lib_index=html_lib_index, - graphing_libs_index=graphing_libs_index, - api_reference_index=api_reference_index, - recipes_index=recipes_index, - enterprise_usage_index=enterprise_usage_index, - enterprise_component_index=enterprise_component_index, - ai_onboarding_index=ai_onboarding_index, - ai_builder_overview_index=ai_builder_overview_index, - ai_builder_integrations_index=ai_builder_integrations_index, - cli_ref_index=cli_ref_index, - mcp_index=mcp_index, - skills_index=skills_index, - width=width, + learn_index=calculate_index(learn, normalized_url), + component_lib_index=calculate_index(component_lib, normalized_url), + frontend_index=calculate_index(frontend, normalized_url), + backend_index=calculate_index(backend, normalized_url), + hosting_index=calculate_index(hosting, normalized_url), + html_lib_index=calculate_index(html_lib, normalized_url), + graphing_libs_index=calculate_index(graphing_libs, normalized_url), + api_reference_index=calculate_index(api_reference, normalized_url), + recipes_index=calculate_index(recipes, normalized_url), + enterprise_usage_index=calculate_index( + enterprise_usage_items, normalized_url + ), + enterprise_component_index=calculate_index( + enterprise_component_items, normalized_url + ), + ai_onboarding_index=calculate_index(ai_onboarding_items, normalized_url), + ai_builder_overview_index=calculate_index( + ai_builder_overview_items, normalized_url + ), + ai_builder_integrations_index=calculate_index( + ai_builder_integrations, normalized_url + ), + mcp_index=calculate_index(mcp_items, normalized_url), + skills_index=calculate_index(skills_items, normalized_url), ), on_mount=rx.call_script(Scrollable_SideBar), id=rx.Var.create("sidebar-container"), From 17109910a100f0a675edcb77a1592064df238495 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Wed, 20 May 2026 03:26:05 -0700 Subject: [PATCH 2/2] memoize sidebar_leaf separately reduce a TON of duplicate inline styles by memoizing SidebarLeaf into a separate component. Definitely more work that could be done to get the overall code size of the sidebar down a bit more even and reduce re-renders when navigating. --- .../templates/docpage/sidebar/sidebar.py | 87 +++++++++++-------- .../templates/docpage/sidebar/state.py | 8 +- 2 files changed, 57 insertions(+), 38 deletions(-) diff --git a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py index aea637157ba..53ba88f6b7f 100644 --- a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py +++ b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py @@ -117,37 +117,14 @@ def sidebar_leaf_guide(is_active: rx.vars.BooleanVar) -> rx.Component: ) +@rx._x.memo def sidebar_leaf( - item: SideBarItem, - url: rx.vars.StringVar[str], - guide_margin_class: str = "ml-[3rem]", + item_names: rx.vars.StringVar[str], + item_link: rx.vars.StringVar[str], + is_active: rx.vars.BooleanVar, + guide_margin_class: rx.vars.StringVar[str], ) -> rx.Component: """Get the leaf node of the sidebar.""" - item.link = item.link.replace("_", "-").rstrip("/") + "/" - is_active = url == item.link - if item.outer: - return rx.el.li( - sidebar_link( - rx.flex( - rx.text( - item.names, - color=rx.cond( - is_active, c_color("violet", 9), c_color("slate", 9) - ), - _hover={ - "color": c_color("slate", 11), - }, - margin="0.5em 0.5em 0.2em 0.5em", - width="100%", - class_name="m-0 transition-color", - ), - ), - href=item.link, - class_name="block w-full", - ), - class_name="m-0 p-0 !overflow-visible w-full list-none", - ) - return rx.el.li( sidebar_link( rx.cond( @@ -160,7 +137,7 @@ def sidebar_leaf( rx.flex( sidebar_leaf_guide(is_active), rx.text( - item.names, + item_names, class_name=rx.cond( is_active, "m-0 text-sm text-primary-10 font-[525] transition-color pl-4", @@ -173,7 +150,7 @@ def sidebar_leaf( "relative pl-4 h-8 flex items-center", ), ), - href=item.link, + href=item_link, class_name=rx.cond( is_active, "block w-full relative", @@ -184,6 +161,35 @@ def sidebar_leaf( ) +@rx._x.memo +def sidebar_leaf_outer( + item_names: rx.vars.StringVar[str], + item_link: rx.vars.StringVar[str], + is_active: rx.vars.BooleanVar, + guide_margin_class: rx.vars.StringVar[str], +) -> rx.Component: + """Get the leaf node of the sidebar.""" + return rx.el.li( + sidebar_link( + rx.flex( + rx.text( + item_names, + color=rx.cond(is_active, c_color("violet", 9), c_color("slate", 9)), + _hover={ + "color": c_color("slate", 11), + }, + margin="0.5em 0.5em 0.2em 0.5em", + width="100%", + class_name="m-0 transition-color", + ), + ), + href=item_link, + class_name="block w-full", + ), + class_name="m-0 p-0 !overflow-visible w-full list-none", + ) + + def sidebar_item_comp( item_index: int, item: SideBarItem, @@ -193,11 +199,20 @@ def sidebar_item_comp( ) -> rx.Component: """Render an item in the sidebar, recursing into its children.""" if not item.children: - return sidebar_leaf( - item=item, - url=url, - guide_margin_class=guide_margin_class, - ) + if item.outer: + return sidebar_leaf_outer( + item_names=item.names, + item_link=item.link, + is_active=(url == item.link), + guide_margin_class=guide_margin_class, + ) + else: + return sidebar_leaf( + item_names=item.names, + item_link=item.link, + is_active=(url == item.link), + guide_margin_class=guide_margin_class, + ) is_open = (index.length() > 0) & (index[0] == item_index) nested_index = rx.cond(is_open, index[1:], []).to(list[int]) @@ -267,8 +282,6 @@ def calculate_index(sidebar_items, url: str) -> list[int]: return index_list url = url.rstrip("/") + "/" - for item in sidebar_items: - item.link = item.link.rstrip("/") + "/" sub = 0 for i, item in enumerate(sidebar_items): if not item.children: diff --git a/docs/app/reflex_docs/templates/docpage/sidebar/state.py b/docs/app/reflex_docs/templates/docpage/sidebar/state.py index 72f3bd68475..a2881ab8298 100644 --- a/docs/app/reflex_docs/templates/docpage/sidebar/state.py +++ b/docs/app/reflex_docs/templates/docpage/sidebar/state.py @@ -21,7 +21,13 @@ class SideBarBase: children: list[SideBarItem] = field(default_factory=list) # Whether the item is a category. Occurs if a single item is at the top level of the sidebar for aesthetics. - outer = False + outer: bool = False + + def __post_init__(self): + """Post initialization processing.""" + if self.link: + # Pre-normalize the link so we don't have to do it in memo'd functions. + self.link = self.link.replace("_", "-").rstrip("/") + "/" class SideBarItem(SideBarBase):