Skip to content

_get_all_imports() does not collect imports from components in props, causing ReferenceError at runtime #6312

@PJ-Snap

Description

@PJ-Snap

Describe the bug

Component._get_all_imports() only walks self.children to collect transitive imports. It does not walk components found in props (e.g. a fallback: rx.Component prop on a third-party component). This means any rx.icon(...) or other component with a library import that is only referenced inside a component-typed prop will be missing its import in the generated JSX, causing a ReferenceError at runtime.

Notably, _get_all_custom_code() and _get_all_dynamic_imports() already call self._get_components_in_props() to handle this case — _get_all_imports() is the only tree-walking method that doesn't.

To Reproduce

  1. Create a component with a fallback: rx.Component prop (or use any third-party component that accepts a component as a prop):
class Show(SomeBase):
    tag = "Show"
    fallback: rx.Component | None = None
  1. Pass an rx.icon(...) inside the fallback prop, where that icon is not used anywhere else on the page:
show(
    rx.text("gated content"),
    when={"feature": "some-feature"},
    fallback=rx.el.div(
        rx.icon("crown", size=14),  # only used here
        rx.text("Upgrade"),
    ),
)
  1. Run the app. The generated JSX will contain:
jsx(LucideCrown, {"size": 14})

but the import line will not include Crown as LucideCrown from lucide-react.

  1. At runtime:
ReferenceError: LucideCrown is not defined
    at Fragment_53c61b1a... (http://localhost:3000/app/routes/...)

If the same icon happens to be used elsewhere on the page (outside the prop), it works — because the import is discovered via the normal children traversal. This makes the bug intermittent and hard to diagnose.

Root Cause

In reflex/components/component.py, _get_all_imports() is:

def _get_all_imports(self, collapse: bool = False) -> ParsedImportDict:
    imports_ = imports.merge_parsed_imports(
        self._get_imports(), *[child._get_all_imports() for child in self.children]
    )
    return imports.collapse_imports(imports_) if collapse else imports_

It only traverses self.children. Compare with _get_all_custom_code() which already handles this:

def _get_all_custom_code(self) -> dict[str, None]:
    code: dict[str, None] = {}
    custom_code = self._get_custom_code()
    if custom_code is not None:
        code[custom_code] = None

    for component in self._get_components_in_props():  # <-- THIS IS MISSING FROM _get_all_imports
        code |= component._get_all_custom_code()

    # ...
    for child in self.children:
        code |= child._get_all_custom_code()
    return code

And _get_all_dynamic_imports() which also handles it:

def _get_all_dynamic_imports(self) -> set[str]:
    dynamic_imports: set[str] = set()
    dynamic_import = self._get_dynamic_imports()
    if dynamic_import:
        dynamic_imports.add(dynamic_import)

    for child in self.children:
        dynamic_imports |= child._get_all_dynamic_imports()

    for component in self._get_components_in_props():  # <-- ALSO MISSING FROM _get_all_imports
        dynamic_imports |= component._get_all_dynamic_imports()

    return dynamic_imports

The infrastructure to discover component-typed props already exists (_get_components_in_props() / _get_component_prop_property). It's just not wired up in _get_all_imports().

Suggested Fix

Add the _get_components_in_props() traversal to _get_all_imports():

def _get_all_imports(self, collapse: bool = False) -> ParsedImportDict:
    imports_ = imports.merge_parsed_imports(
        self._get_imports(),
        *[child._get_all_imports() for child in self.children],
        *[comp._get_all_imports() for comp in self._get_components_in_props()],
    )
    return imports.collapse_imports(imports_) if collapse else imports_

This is consistent with how _get_all_custom_code() and _get_all_dynamic_imports() already work.

Workaround

Use DynamicIcon which resolves icons by name at runtime via lucide-react/dynamic.mjs and needs no static import:

from reflex.components.lucide.icon import DynamicIcon

DynamicIcon.create(name="crown", size=14)

Specifics

  • Reflex version: 0.8.27
  • Python: 3.12
  • OS: Windows 10 / Node 24.9.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions