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
- 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
- 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"),
),
)
- 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.
- 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
Describe the bug
Component._get_all_imports()only walksself.childrento collect transitive imports. It does not walk components found in props (e.g. afallback: rx.Componentprop on a third-party component). This means anyrx.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 aReferenceErrorat runtime.Notably,
_get_all_custom_code()and_get_all_dynamic_imports()already callself._get_components_in_props()to handle this case —_get_all_imports()is the only tree-walking method that doesn't.To Reproduce
fallback: rx.Componentprop (or use any third-party component that accepts a component as a prop):rx.icon(...)inside thefallbackprop, where that icon is not used anywhere else on the page:but the import line will not include
Crown as LucideCrownfromlucide-react.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
childrentraversal. This makes the bug intermittent and hard to diagnose.Root Cause
In
reflex/components/component.py,_get_all_imports()is:It only traverses
self.children. Compare with_get_all_custom_code()which already handles this:And
_get_all_dynamic_imports()which also handles it: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():This is consistent with how
_get_all_custom_code()and_get_all_dynamic_imports()already work.Workaround
Use
DynamicIconwhich resolves icons by name at runtime vialucide-react/dynamic.mjsand needs no static import:Specifics