Skip to content

fix(compiler): treat underscore-prefixed JSX tags as components#36703

Open
ousamabenyounes wants to merge 2 commits into
facebook:mainfrom
ousamabenyounes:fix/compiler-underscore-prefixed-components
Open

fix(compiler): treat underscore-prefixed JSX tags as components#36703
ousamabenyounes wants to merge 2 commits into
facebook:mainfrom
ousamabenyounes:fix/compiler-underscore-prefixed-components

Conversation

@ousamabenyounes
Copy link
Copy Markdown

@ousamabenyounes ousamabenyounes commented Jun 6, 2026

Summary

Fixes #36601

The React Compiler was using a regex check /^[A-Z]/ to determine whether a JSX tag is a component or a builtin (host) element. This meant that tags prefixed with underscore (e.g., <_Bar>) were incorrectly treated as builtin elements.

Per the JSX specification, only tags starting with a lowercase letter (a-z) should be treated as host/builtin elements. Tags starting with anything else (uppercase letters, underscore, dollar sign, etc.) should be treated as component references.

The Bug

Given this input:

function Foo({ _Bar }: { _Bar: React.ComponentType }) {
    return (
      <_Bar>
        {() => <div />}
      </_Bar>
    );
}

The compiler was incorrectly treating <_Bar> as a builtin tag, which caused the children {() => <div />} to be incorrectly extracted into a separate _temp function — silently changing the runtime behavior from what worked fine before React Compiler.

The Fix

Changed the condition in lowerJsxElementName() in BuildHIR.ts from:

if (tag.match(/^[A-Z]/)) {
  // component
} else {
  // builtin tag
}

To:

if (tag.match(/^[a-z]/)) {
  // builtin tag
} else {
  // component
}

This matches the React JSX transform convention where a JSX tag is a host element only if its name starts with a lowercase ASCII letter.

Testing

Existing test fixtures (builtin-jsx-tag-lowered-between-mutations, global-jsx-tag-lowered-between-mutations) continue to work correctly since:

  • <div> (lowercase) → still treated as builtin ✓
  • <View> (uppercase) → still treated as component ✓

Test verification (RED → GREEN)

Added fixture compiler/.../fixtures/compiler/jsx-underscore-prefixed-tag-is-component.tsx (+ generated .expect.md).

Command: yarn snap -p '*underscore*'

GREEN — with the fix (/^[a-z]/): _Bar is lowered as a component (reactive dependency), sprout eval matches.

1 Tests, 1 Passed, 0 Failed

RED — fix reverted to /^[A-Z]/:

FAIL: jsx-underscore-prefixed-tag-is-component
Found differences in evaluator results
Non-forget (expected): (kind: ok) <div>{"children":"ok"}</div>
Forget:                (kind: exception) _Bar is not defined
1 Tests, 0 Passed, 1 Failed

Without the patch, <_Bar> is lowered as a BuiltinTag (host string), so the compiled output references an undefined _Bar and throws at runtime — diverging from the non-compiled reference. The fixture catches exactly this regression.

The React Compiler was using a regex check `/^[A-Z]/` to determine whether
a JSX tag is a component or a builtin (host) element. This meant that tags
prefixed with underscore (e.g., `<_Bar>`) were incorrectly treated as
builtin elements, even though the JSX specification states that only tags
starting with a lowercase letter should be treated as host elements.

This change flips the condition to check for lowercase first letters
(`/^[a-z]/`) for builtin tags, treating everything else (uppercase,
underscore-prefixed, dollar-prefixed, etc.) as component references, which
matches the React JSX transform behavior.

Fixes facebook#36601
@meta-cla meta-cla Bot added the CLA Signed label Jun 6, 2026
The fix shipped without a fixture. Adds a snapshot test exercising the facebook#36601
repro: a `<_Bar>` JSX tag must be lowered as a component reference, not a
BuiltinTag.

RED (fix reverted to `/^[A-Z]/`): the fixture fails — Forget output throws
"_Bar is not defined" vs the non-Forget `<div>{"children":"ok"}</div>`, because
`_Bar` is emitted as a host string tag.
GREEN (fix `/^[a-z]/`): `_Bar` is lowered as a reactive component dependency;
snap passes (1 Tests, 1 Passed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Compiler Bug]: Components prefixed with _ are assumed host components

1 participant