Skip to content

Refactor sidebar to use reactive Var operations instead of Python conditionals#6528

Merged
Alek99 merged 3 commits into
mainfrom
claude/migrate-sidebar-to-memo-9bnis
May 20, 2026
Merged

Refactor sidebar to use reactive Var operations instead of Python conditionals#6528
Alek99 merged 3 commits into
mainfrom
claude/migrate-sidebar-to-memo-9bnis

Conversation

@masenf
Copy link
Copy Markdown
Collaborator

@masenf masenf commented May 19, 2026

Type of change

  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update

Description

This PR refactors the sidebar component to use Reflex's reactive Var operations instead of Python-level conditionals, enabling better runtime reactivity and compilation to optimized React code.

Key changes:

  1. Type annotations: Updated function signatures to use rx.vars.StringVar[str], rx.vars.ArrayVar[list[int]], and rx.Var[bool] for reactive parameters instead of plain Python types.

  2. Reactive conditionals: Replaced Python if/else expressions with rx.cond() for dynamic UI rendering:

    • sidebar_leaf_guide(): Now uses rx.cond() for conditional rendering
    • sidebar_leaf(): Converted all conditional styling to rx.cond() calls
    • sidebar_category(): Uses rx.cond() for active state styling
    • sidebar_comp(): Replaced complex nested Python conditionals with reactive rx.cond() chains
  3. Reactive comparisons: Updated logic to use Var operations:

    • is_open calculation now uses index.length() > 0 and bitwise & operator
    • URL checks use .startswith() and .contains() methods on Var objects
    • Replaced string concatenation with direct integer indices
  4. Memoization: Added @rx._x.memo decorator to sidebar_comp() to compile the reactive tree into a single optimized React component that receives runtime props.

  5. Separation of concerns: The sidebar() wrapper function remains a regular Python function that handles non-reactive operations (URL normalization, index calculation), while sidebar_comp() handles all reactive rendering logic.

  6. Removed unused imports: Removed cli_ref from imports as it's no longer used.

  7. Documentation: Added comprehensive docstrings explaining the memoization strategy and the division between reactive and non-reactive logic.

Motivation

This refactoring enables the sidebar to respond to runtime URL and index changes without re-executing Python code, resulting in better performance and more idiomatic Reflex patterns. The memoized component compiles to a single React component that receives these values as props, rather than recalculating the entire tree on each change.

Testing

Existing sidebar functionality is preserved. The changes are internal refactoring to use reactive patterns rather than Python conditionals. All sidebar rendering behavior remains identical from the user's perspective.

https://claude.ai/code/session_013mppZsm9QZVz6DTPYokXR2

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
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 19, 2026

Merging this PR will not alter performance

✅ 24 untouched benchmarks


Comparing claude/migrate-sidebar-to-memo-9bnis (1710991) with main (fc7221a)

Open in CodSpeed

masenf added 2 commits May 20, 2026 01:37
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.
@masenf masenf marked this pull request as ready for review May 20, 2026 10:27
@masenf masenf requested review from a team and Alek99 as code owners May 20, 2026 10:27
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 20, 2026

Greptile Summary

This PR refactors the docs sidebar to replace Python-level if/else conditionals with Reflex reactive rx.cond() / Var operations and applies @rx._x.memo to sidebar_leaf, sidebar_leaf_outer, and sidebar_comp, compiling the component tree into a single memoized React component driven by runtime props. Link normalization is moved from scattered per-render mutations into SideBarBase.__post_init__.

  • Reactive conversion: All conditional class names, active states, and URL checks are now expressed as Var operations (url.startswith(...), url.contains(...), index.length() > 0, bitwise &/~), enabling runtime reactivity without re-running Python.
  • Memoization: @rx._x.memo on sidebar_comp and the two leaf functions means the compiled React component receives url and per-section index arrays as props rather than re-expanding the full Python component tree on each change.
  • Separation of concerns: The public sidebar() wrapper remains a plain Python function that handles URL normalization and index calculation, then hands off to the reactive sidebar_comp.

Confidence Score: 4/5

The refactor is behaviorally correct and the reactive conversion is well-structured; two dead parameters (one on a public function signature) are the only notable rough edges.

The reactive Var operations faithfully replicate the old Python conditionals, link normalization is moved cleanly to post_init, and the memoization boundaries are well-reasoned. The guide_margin_class parameter on sidebar_leaf_outer is declared and required by all callers but never read inside the function body, wasting memo cache slots. The width parameter on the public sidebar() function is still accepted but silently discarded, so any caller passing width= will see no effect.

docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py — the unused guide_margin_class parameter on sidebar_leaf_outer and the dangling width parameter on sidebar() both deserve a second look before merge.

Important Files Changed

Filename Overview
docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py Major refactor converting Python-level conditionals to reactive rx.cond() / Var operations and adding @rx._x.memo. Two minor issues: guide_margin_class is an unused dead parameter in sidebar_leaf_outer, and the width parameter on sidebar() is no longer forwarded and thus silently ignored.
docs/app/reflex_docs/templates/docpage/sidebar/state.py Adds post_init to SideBarBase to pre-normalize link strings at construction time, removing the need for per-render mutation. Clean, correct change.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["sidebar(url, width)\n[plain Python]"] --> B["normalize_url(url)"]
    A --> C["calculate_index(section, url)\n17 sections"]
    B --> D
    C --> D
    D["sidebar_comp(url_var, index_vars)\n[@rx._x.memo - React component]"]
    D --> E{"url.startswith?"}
    E -->|"/hosting/"| F["hosting_categories + hosting_content"]
    E -->|"/ai/"| G["ai_builder_categories + rx.cond(is_ai_mcp_or_skills, ...)"]
    E -->|else| H{"url.contains?"}
    H -->|library or mcp-| I["library_content"]
    H -->|api-reference| J["api_reference_content"]
    H -->|enterprise| K["enterprise_content"]
    H -->|else| L["default_docs_content"]
    D --> M["sidebar_item_comp - Python recursion at compile time"]
    M --> N["sidebar_leaf @rx._x.memo"]
    M --> O["sidebar_leaf_outer @rx._x.memo"]
    N --> P["sidebar_leaf_guide - plain helper using rx.cond"]
Loading

Reviews (1): Last reviewed commit: "memoize sidebar_leaf separately" | Re-trigger Greptile

Comment on lines +164 to +171
@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."""
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Unused guide_margin_class parameter in sidebar_leaf_outer

guide_margin_class is declared as a required StringVar parameter on the @rx._x.memo function but is never referenced in the function body. Because the memo system uses all parameter values as cache keys, passing a varying guide_margin_class string (e.g., "ml-[2.5rem]" vs "ml-[3rem]") will produce separate memoized React components with identical output, defeating the purpose of memoization and wasting memory.

@@ -709,54 +743,40 @@ def sidebar_comp(


def sidebar(url=None, width: str = "100%") -> rx.Component:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 width parameter silently ignored in sidebar

The width parameter is declared on the public sidebar() function and was previously forwarded to sidebar_comp(), but sidebar_comp no longer accepts it. Any caller that passes width= now has that value silently dropped, and the sidebar wrapper's rx.box doesn't apply it either. If width customisation is no longer needed, the parameter should be removed to prevent confusion; if it is still needed, it should be applied to the outer box.

Suggested change
def sidebar(url=None, width: str = "100%") -> rx.Component:
def sidebar(url=None) -> rx.Component:

@Alek99 Alek99 merged commit a339f76 into main May 20, 2026
70 checks passed
@Alek99 Alek99 deleted the claude/migrate-sidebar-to-memo-9bnis branch May 20, 2026 18:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants