Fix rx.code_block rendering empty when content is a state Var#6481
Fix rx.code_block rendering empty when content is a state Var#6481jryberg wants to merge 1 commit into
Conversation
When a component routes its content through `props.children` (notably
`rx.code_block` via `CodeBlock._render`, which moves the user's first
positional arg into `props["children"]`), the compiler was still
emitting positional children in the generated `jsx()` call. The
auto-memo wrapper produced for a stateful subtree destructures
`({children})` from its own (empty) props and passes that undefined
value as the third positional arg:
jsx(SyntaxHighlighter, {children: state_var, ...}, children)
`@emotion/react`'s classic `jsx` delegates to
`React.createElement.apply(void 0, args)`, and
`React.createElement(type, props, undefined)` has `arguments.length === 3`,
so React sets `props.children = undefined` — overriding the legitimate
value. `SyntaxHighlighter` then renders the language-class wrapper with
no content.
Skip the positional-children emission in `_RenderUtils.render_tag`
whenever `props` already declares a `children:` key. Regular components
that do not bind `props.children` continue to receive their rendered
subtree positionally.
closes reflex-dev#6480
Greptile SummaryThis PR fixes a bug where
Confidence Score: 4/5The change is narrowly scoped to JSX code-generation for components that route content through props.children; it fixes a real rendering bug without touching state management, event handling, or network code. The fix is correct for the stated bug and the two new tests are well-targeted. The only open question is whether silently discarding rendered_children beyond the auto-memo placeholder could ever affect a future component — there is no assertion or warning to surface that case. packages/reflex-base/src/reflex_base/compiler/templates.py — specifically the new early-return branch in render_tag and whether its silent-drop of positional children could affect any components beyond CodeBlock. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["render_tag(component)"] --> B["Extract props_list\nand rendered_children"]
B --> C{"Any prop\nstartswith 'children:'?"}
C -- Yes --> D["jsx(name, {props})\n(positional children dropped)"]
C -- No --> E["jsx(name, {props}, child1, child2, ...)"]
D --> F["React reads children\nfrom props object"]
E --> G["React uses positional\nchildren as props.children"]
style D fill:#90EE90
style F fill:#90EE90
Reviews (1): Last reviewed commit: "Fix rx.code_block rendering empty when c..." | Re-trigger Greptile |
| if any(p.startswith("children:") for p in props_list): | ||
| return f"jsx({name},{props})" |
There was a problem hiding this comment.
The guard correctly drops positional children when
children: is already in props, but it does so unconditionally even if rendered_children contains real rendered elements beyond the auto-memo placeholder. If any future component's _render() places state content in props["children"] while also returning genuine child nodes in the children array, those nodes will be silently discarded with no diagnostic. A lightweight check here makes the intent explicit and prevents a hard-to-debug silent regression.
| if any(p.startswith("children:") for p in props_list): | |
| return f"jsx({name},{props})" | |
| if any(p.startswith("children:") for p in props_list): | |
| # rendered_children should only contain the auto-memo placeholder | |
| # ("children") when this path is taken; log a warning if real | |
| # elements were also present so the mismatch is not silently lost. | |
| non_placeholder = [c for c in rendered_children if c != "children"] | |
| if non_placeholder: | |
| import warnings | |
| warnings.warn( | |
| f"render_tag: component '{name}' declares children in props " | |
| f"but also has {len(non_placeholder)} positional child(ren) " | |
| "that will be dropped. This is likely a bug in _render().", | |
| stacklevel=2, | |
| ) | |
| return f"jsx({name},{props})" |
|
I think this was already fixed here #6466 |
|
since we already have a more general fix for this in |
|
We have released the fix for this in 0.9.2.post1, please upgrade and confirm that the issue you were seeing is resolved. I filed #6484 as an enhancement to add the true integration tests for code_block component. Will close this PR now, but we appreciate your involvement in the project 😌 |
Type of change
Summary
Fixes #6480.
rx.code_block(state_var, language=..., show_line_numbers=True)renders an empty<pre><code class="language-yaml">element. The state Var is populated at runtime —rx.set_clipboard(state_var)copies the full content, andrx.cond(state_var != "", ...)evaluatesTrue— butSyntaxHighlighterreceives nothing.Root cause
CodeBlock._render()moves the user's first positional argument intoprops["children"]. For a stateful subtree, the compiler wraps the call in an auto-memo:The third positional
childrenis the wrapper's destructured prop, which isundefinedwhen the wrapper is invoked with no children (always, forrx.code_block).@emotion/react's classicjsxdelegates toReact.createElement.apply(void 0, args), andReact.createElement(type, props, undefined)hasarguments.length === 3, so React setsprops.children = undefined, overriding the legitimate value from the props object.Fix
In
_RenderUtils.render_tag(packages/reflex-base/src/reflex_base/compiler/templates.py), whenpropsalready declares achildren:key, omit the positional children in the emittedjsx()call. Components that don't bindprops.childrenkeep their existing behaviour.Tests
Two new tests in
tests/units/compiler/test_compiler.py:test_render_tag_skips_positional_children_when_props_have_children— direct regression for rx.code_block renders empty when content is a state Var (0.9.2) #6480.test_render_tag_preserves_positional_children_for_normal_components— guards the common path so a future refactor can't re-introduce the bug by over-correcting.uv run pytest tests/units/compiler/— 160 passed.Checklist
uv run pytest tests/units/compiler/test_compiler.py -k render_tagrx.code_block(state_var, ...)across three independent call sites in a downstream application (Reflex 0.9.2 + the equivalent patch applied via post-install script).