diff --git a/reflex/compiler/compiler.py b/reflex/compiler/compiler.py index a097b2d99f2..21b66a52941 100644 --- a/reflex/compiler/compiler.py +++ b/reflex/compiler/compiler.py @@ -410,7 +410,11 @@ def _get_shared_components_recursive( # When the component is referenced by more than one page, render it # to be included in the STATEFUL_COMPONENTS module. # Skip this step in dev mode, thereby avoiding potential hot reload errors for larger apps - if isinstance(component, StatefulComponent) and component.references > 1: + if ( + isinstance(component, StatefulComponent) + and component.references > 1 + and is_prod_mode() + ): # Reset this flag to render the actual component. component.rendered_as_shared = False diff --git a/tests/units/compiler/test_compiler.py b/tests/units/compiler/test_compiler.py index 1a0f87b89a3..1e973ce9009 100644 --- a/tests/units/compiler/test_compiler.py +++ b/tests/units/compiler/test_compiler.py @@ -1,6 +1,7 @@ import importlib.util import os from pathlib import Path +from unittest.mock import patch import pytest from pytest_mock import MockerFixture @@ -8,6 +9,7 @@ from reflex import constants from reflex.compiler import compiler, utils from reflex.components.base import document +from reflex.components.component import StatefulComponent from reflex.components.el.elements.metadata import Link from reflex.constants.compiler import PageNames from reflex.utils.imports import ImportVar, ParsedImportDict @@ -448,3 +450,34 @@ def test_create_document_root_with_meta_viewport(): assert str(root.children[0].children[2].name) == '"viewport"' # pyright: ignore [reportAttributeAccessIssue] assert str(root.children[0].children[2].content) == '"foo"' # pyright: ignore [reportAttributeAccessIssue] assert str(root.children[0].children[3].char_set) == '"utf-8"' # pyright: ignore [reportAttributeAccessIssue] + + +@pytest.mark.parametrize("prod_mode", [True, False]) +def test_shared_stateful_components_only_in_prod(prod_mode: bool): + """Shared stateful components must only be marked rendered_as_shared in prod mode. + + In dev mode, stateful component compilation is skipped so the shared module + is empty. Marking components as rendered_as_shared in dev mode causes them to + import from a non-existent module, silently breaking rendering. + + Args: + prod_mode: Whether to simulate prod mode. + """ + from reflex.components.component import Component + + component = StatefulComponent( + tag="TestComponent", + references=2, # referenced by more than one page + children=[], + component=Component.create(), + ) + + rendered_components: dict[str, None] = {} + all_import_dicts: list[ParsedImportDict] = [] + + with patch("reflex.compiler.compiler.is_prod_mode", return_value=prod_mode): + compiler._get_shared_components_recursive( + component, rendered_components, all_import_dicts + ) + + assert component.rendered_as_shared is prod_mode