From 6b2cf354687b15ca1cc2907da20d6c4b13bcbbe8 Mon Sep 17 00:00:00 2001 From: Benedikt Bartscher Date: Sun, 19 Oct 2025 15:24:38 +0200 Subject: [PATCH 1/4] fix: properly initialize rx.Field annotated backend vars in mixin states --- reflex/state.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/reflex/state.py b/reflex/state.py index 629c2e2914..92dfbcac9a 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -523,13 +523,15 @@ def __init_subclass__(cls, mixin: bool = False, **kwargs): new_backend_vars = { name: value if not isinstance(value, Field) else value.default_value() - for name, value in list(cls.__dict__.items()) + for mixin_cls in [*cls._mixins(), cls] + for name, value in list(mixin_cls.__dict__.items()) if types.is_backend_base_variable(name, cls) } # Add annotated backend vars that may not have a default value. new_backend_vars.update({ name: cls._get_var_default(name, annotation_value) - for name, annotation_value in cls._get_type_hints().items() + for mixin_cls in [*cls._mixins(), cls] + for name, annotation_value in mixin_cls._get_type_hints().items() if name not in new_backend_vars and types.is_backend_base_variable(name, cls) }) @@ -579,9 +581,6 @@ def __init_subclass__(cls, mixin: bool = False, **kwargs): cls.computed_vars[name] = newcv cls.vars[name] = newcv continue - if types.is_backend_base_variable(name, mixin_cls): - cls.backend_vars[name] = copy.deepcopy(value) - continue if events.get(name) is not None: continue if not cls._item_is_event_handler(name, value): From bfc32f6cc96729726512cffe2b2397d768262f1d Mon Sep 17 00:00:00 2001 From: Benedikt Bartscher Date: Sun, 19 Oct 2025 17:49:32 +0200 Subject: [PATCH 2/4] fix: check mixin_cls instead of cls --- reflex/state.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reflex/state.py b/reflex/state.py index 92dfbcac9a..a2cdc54862 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -525,7 +525,7 @@ def __init_subclass__(cls, mixin: bool = False, **kwargs): name: value if not isinstance(value, Field) else value.default_value() for mixin_cls in [*cls._mixins(), cls] for name, value in list(mixin_cls.__dict__.items()) - if types.is_backend_base_variable(name, cls) + if types.is_backend_base_variable(name, mixin_cls) } # Add annotated backend vars that may not have a default value. new_backend_vars.update({ @@ -533,7 +533,7 @@ def __init_subclass__(cls, mixin: bool = False, **kwargs): for mixin_cls in [*cls._mixins(), cls] for name, annotation_value in mixin_cls._get_type_hints().items() if name not in new_backend_vars - and types.is_backend_base_variable(name, cls) + and types.is_backend_base_variable(name, mixin_cls) }) cls.backend_vars = { From 86d3a4721979cf6fafd4aa6bda693f0837effa93 Mon Sep 17 00:00:00 2001 From: Benedikt Bartscher Date: Mon, 20 Oct 2025 20:28:52 +0200 Subject: [PATCH 3/4] add some basic tests --- tests/units/test_state.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/tests/units/test_state.py b/tests/units/test_state.py index 46240fb95c..091badf695 100644 --- a/tests/units/test_state.py +++ b/tests/units/test_state.py @@ -110,7 +110,14 @@ class Object(Base): prop2: str = "hello" -class TestState(BaseState): +class TestMixin(BaseState, mixin=True): + """A test mixin.""" + + mixin: rx.Field[str] = rx.field("mixin_value") + _mixin_backend: rx.Field[int] = rx.field(default_factory=lambda: 10) + + +class TestState(TestMixin, BaseState): # pyright: ignore[reportUnsafeMultipleInheritance] """A test state.""" # Set this class as not test one @@ -342,6 +349,7 @@ def test_class_vars(test_state): "fig", "dt", "asynctest", + "mixin", } @@ -367,7 +375,7 @@ def test_event_handlers(test_state): assert all(key in cls.event_handlers for key in expected_keys) -def test_default_value(test_state): +def test_default_value(test_state: TestState): """Test that the default value of a var is correct. Args: @@ -378,6 +386,10 @@ def test_default_value(test_state): assert test_state.key == "" assert test_state.sum == 3.15 assert test_state.upper == "" + assert test_state._backend == 0 + assert test_state.mixin == "mixin_value" + assert test_state._mixin_backend == 10 + assert test_state.array == [1, 2, 3.15] def test_computed_vars(test_state): @@ -735,7 +747,7 @@ def test_set_dirty_substate( assert grandchild_state.dirty_vars == set() -def test_reset(test_state, child_state): +def test_reset(test_state: TestState, child_state: ChildState): """Test resetting the state. Args: @@ -771,6 +783,8 @@ def test_reset(test_state, child_state): "mapping", "dt", "_backend", + "mixin", + "_mixin_backend", "asynctest", } From ee35403e840305024b6eaedca3be23d62275b63d Mon Sep 17 00:00:00 2001 From: Benedikt Bartscher Date: Mon, 20 Oct 2025 20:31:53 +0200 Subject: [PATCH 4/4] i missed this one --- tests/units/utils/test_format.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/units/utils/test_format.py b/tests/units/utils/test_format.py index 0c2b148c5d..aea7ef3b2c 100644 --- a/tests/units/utils/test_format.py +++ b/tests/units/utils/test_format.py @@ -602,6 +602,7 @@ def test_format_query_params(input, output): "key" + FIELD_MARKER: "", "map_key" + FIELD_MARKER: "a", "mapping" + FIELD_MARKER: {"a": [1, 2, 3], "b": [4, 5, 6]}, + "mixin" + FIELD_MARKER: "mixin_value", "num1" + FIELD_MARKER: 0, "num2" + FIELD_MARKER: 3.15, "obj" + FIELD_MARKER: {"prop1": 42, "prop2": "hello"},