Skip to content

Add displayName to React components for DevTools visibility - #6945

Open
masenf wants to merge 5 commits into
mainfrom
claude/displayname-context-memo-vjfh3a
Open

Add displayName to React components for DevTools visibility#6945
masenf wants to merge 5 commits into
mainfrom
claude/displayname-context-memo-vjfh3a

Conversation

@masenf

@masenf masenf commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Type of change

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

Description

This PR adds displayName properties to all compiled React components and contexts, improving visibility in React DevTools. Without explicit display names, DevTools shows generic labels like Context.Provider, Anonymous, or Component for every instance in the tree, making it difficult to debug component hierarchies.

Changes:

  1. Context template (context_template): Added displayName assignments for:

    • Built-in contexts: ColorModeContext, UploadFilesContext, DispatchContext, EventLoopContext
    • State contexts: Named as StateContext(python.state.name) to identify which Python state they carry
    • ClientSide wrapper: Now a named function component with display name reflecting the wrapped component
  2. Page template (page_template): Every page compiles to a component named Component, so the route is now carried in its displayName (e.g., Component(about), Component(test/[dynamic])) to distinguish pages in DevTools

  3. Memo components (_render_memo_component): Added displayName assignment to exported memoized components, using either:

    • The Python function name for @rx.memo decorated components
    • The wrapped Python class name for auto-memoized components (instead of the hash-suffixed tag)
  4. MemoComponentDefinition: Added optional display_name field to carry the name React DevTools should show

  5. Dynamic imports: Updated ClientSide() calls to pass the component name as a second argument, enabling the wrapper to set an appropriate display name

  6. Theme context (react-theme.js): Added displayName to the theme context

Test coverage

  • Added 5 new unit tests in test_compiler.py covering context display names, page display names, and the ClientSide wrapper naming
  • Added 3 new unit tests in test_memo.py covering memo display names, custom wrappers, and string escaping
  • Added 1 new unit test in test_memoize_plugin.py covering auto-memo display names
  • All existing tests pass

Checklist

  • Tests pass with adequate coverage
  • uv run ruff check . and uv run ruff format . clean
  • uv run pyright reflex tests passes
  • pyi_hashes.json updated
  • News fragment added

https://claude.ai/code/session_01W4Psp6qZsEpbuRePuRVkYX

Review in cubic

claude added 2 commits August 25, 2026 11:10
React DevTools labels a component from its `displayName` (or the name of the
function it wraps). The compiled frontend supplied neither: memo bodies are
anonymous arrow functions passed to `memo()`, which erases the name JS would
otherwise infer from the assignment, and none of the generated contexts were
named. The result was a tree of `Anonymous` entries under an unlabelled stack
of `Context.Provider`s.

Each generated memo module now assigns a `displayName` to its export:
`@rx.memo` components use the decorated function's name, and auto-memoized
wrappers use the Python class they wrap rather than their content-hashed tag
(`Foreach`, not `Foreach_comp_70fd2c58...`). The name is carried on
`MemoComponentDefinition.display_name` and JSON-escaped at render time.

Every context in the generated `context.js` is named too — `ColorModeContext`,
`UploadFilesContext`, `DispatchContext`, `EventLoopContext`, `ThemeContext`,
and one per state (`StateContext(<full state name>)`) — and the `ClientSide`
HOC returns a named component tagged with the client-only component it loads.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W4Psp6qZsEpbuRePuRVkYX
Every page compiles to a component named `Component`, so the DevTools tree
showed the same label whichever route was mounted. `page_template` now takes
the route and emits `Component.displayName = "Component(<route>)"`, threaded
through both the page-context and legacy compile paths.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W4Psp6qZsEpbuRePuRVkYX
@masenf
masenf requested a review from a team as a code owner August 25, 2026 19:16
@codspeed-hq

codspeed-hq Bot commented Aug 25, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 27 untouched benchmarks
⏩ 8 skipped benchmarks1


Comparing claude/displayname-context-memo-vjfh3a (0cb0d82) with main (d3a17c7)

Open in CodSpeed

Footnotes

  1. 8 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@greptile-apps

greptile-apps Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds readable React DevTools names to generated pages, contexts, client-only wrappers, and memoized components.

  • Assigns route- and state-specific display names in generated frontend modules.
  • Propagates Python component names through memo metadata and generated memo exports.
  • Names dynamically imported client-only wrappers and adds focused compiler tests.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
packages/reflex-base/src/reflex_base/compiler/templates.py Generates escaped display names for contexts, pages, client-side wrappers, and memo component exports.
packages/reflex-base/src/reflex_base/components/component.py Passes NoSSR component names into generated ClientSide wrappers without changing their loading behavior.
packages/reflex-base/src/reflex_base/components/memo.py Adds optional memo display-name metadata and assigns readable class names to auto-memoized wrappers.
reflex/compiler/compiler.py Propagates route and memo display-name metadata into frontend template rendering.
reflex/compiler/utils.py Includes memo display names in the generated component definition data.
tests/units/compiler/test_compiler.py Covers generated context, page, and client-side wrapper display names.
tests/units/components/test_memo.py Covers memo display-name generation, custom wrappers, and JavaScript string escaping.
tests/units/compiler/test_memoize_plugin.py Verifies readable names for automatically memoized component wrappers.

Reviews (3): Last reviewed commit: "chore: add per-package changelog fragmen..." | Re-trigger Greptile

Comment thread packages/reflex-base/src/reflex_base/compiler/templates.py

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

All reported issues were addressed across 12 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/reflex-base/src/reflex_base/compiler/templates.py
claude added 3 commits August 25, 2026 19:28
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W4Psp6qZsEpbuRePuRVkYX
React Router's `decorateComponentExportsWithProps` rewrites an exported
function *declaration* into a function *expression* wrapped in
`UNSAFE_withComponentProps`, so `export default function Component() {}`
leaves no module-scope `Component` binding. The trailing
`Component.displayName = ...` then threw `ReferenceError: Component is not
defined` as soon as a route module loaded, breaking every page.

Declare the function, name it, and export the identifier instead. React Router
then wraps the binding rather than the declaration, the assignment resolves,
and the wrapper renders `Component` as a child so the route name still shows in
the DevTools tree.

Verified against a real `react-router build` (8.3.0): the old shape's route
chunk throws `ReferenceError: Component is not defined` on evaluation, the new
one evaluates and keeps `Component.displayName`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W4Psp6qZsEpbuRePuRVkYX
The changelog check runs towncrier per affected package, so reflex-base and
reflex-components-plotly each need their own fragment alongside the root one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W4Psp6qZsEpbuRePuRVkYX
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.

2 participants