From 3bcac1cdc540cb6e64ae48e3b063b205d4469e88 Mon Sep 17 00:00:00 2001 From: Andreas Eismann Date: Wed, 18 Feb 2026 15:56:58 +0100 Subject: [PATCH 1/5] feat: Add function to skip serialization. --- reflex/state.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/reflex/state.py b/reflex/state.py index 6ba5633b5e6..ee596ce1051 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -2059,6 +2059,9 @@ def _dirty_computed_vars( if include_backend or not self.computed_vars[cvar]._backend } + def __skip_serialization(self) -> bool: + return False + def get_delta(self) -> Delta: """Get the delta for the state. @@ -2067,6 +2070,9 @@ def get_delta(self) -> Delta: """ delta = {} + if self.__skip_serialization(): + return delta + self._mark_dirty_computed_vars() frontend_computed_vars: set[str] = { name for name, cv in self.computed_vars.items() if not cv._backend @@ -2203,6 +2209,9 @@ def dict( Returns: The object as a dictionary. """ + if not initial and self.__skip_serialization(): + return {} + if include_computed: self._mark_dirty_computed_vars() base_vars = { From cc623eb126b8510df201ea2527fcb5254a65f9a1 Mon Sep 17 00:00:00 2001 From: Andreas Eismann Date: Fri, 20 Feb 2026 09:50:44 +0100 Subject: [PATCH 2/5] fix: Adding tests. Refactoring. Bot comments solved. --- reflex/state.py | 12 ++++++-- tests/units/test_state.py | 63 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/reflex/state.py b/reflex/state.py index ee596ce1051..c7200f5c377 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -2059,7 +2059,13 @@ def _dirty_computed_vars( if include_backend or not self.computed_vars[cvar]._backend } - def __skip_serialization(self) -> bool: + @property + def _skip_serialization(self) -> bool: + """Whether to skip serialization for this state. + + Override in a subclass to skip sending state updates to the frontend. + Useful e.g. for permission/role-based state visibility. + """ return False def get_delta(self) -> Delta: @@ -2070,7 +2076,7 @@ def get_delta(self) -> Delta: """ delta = {} - if self.__skip_serialization(): + if self._skip_serialization: return delta self._mark_dirty_computed_vars() @@ -2209,7 +2215,7 @@ def dict( Returns: The object as a dictionary. """ - if not initial and self.__skip_serialization(): + if not initial and self._skip_serialization: return {} if include_computed: diff --git a/tests/units/test_state.py b/tests/units/test_state.py index 5802cfa8b6f..738eb27e276 100644 --- a/tests/units/test_state.py +++ b/tests/units/test_state.py @@ -4470,3 +4470,66 @@ async def test_rebind_mutable_proxy(mock_app: rx.App, token: str) -> None: assert state.data["a"] == [2, 3] # Object identity persists across serialization, so data["b"] is also mutated. assert state.data["b"] == [2, 3] + + +class NormalState(rx.State): + value: rx.Field[int] = rx.field(42) + + +class SkippedState(NormalState): + skipped_value: rx.Field[int] = rx.field(43) + + @property + def _skip_serialization(self) -> bool: + return True + + +class SkippedSubState(SkippedState): + substate_value: rx.Field[int] = rx.field(44) + + +def test_default_skip_serialization_is_false(): + state = NormalState(_reflex_internal_init=True) + assert state._skip_serialization is False + + +def test_subclass_override_is_respected(): + """Subclass override must actually take effect.""" + assert SkippedState(_reflex_internal_init=True)._skip_serialization is True + assert NormalState(_reflex_internal_init=True)._skip_serialization is False + + +def test_dict_contains_value_by_default(): + state = NormalState(_reflex_internal_init=True) + state_dict = str(state.dict()) + assert "42" in state_dict + assert "43" not in state_dict + assert "44" not in state_dict + + +def test_dict_empty_when_skip_serialization(): + state = SkippedState(_reflex_internal_init=True) + assert state.dict() == {} + + +def test_get_delta_contains_value_after_change(): + state = NormalState(_reflex_internal_init=True) + state.value = 99 + state_delta = str(state.get_delta()) + assert "99" in state_delta + assert "43" not in state_delta + assert "44" not in state_delta + + +def test_get_delta_empty_when_skip_serialization(): + state = SkippedState(_reflex_internal_init=True) + state.skipped_value = 99 + assert state.get_delta() == {} + + +def test_substate_of_skipped_parent_is_also_skipped(): + """Substates of a skipped parent are also skipped, even without overriding _skip_serialization.""" + state = SkippedSubState(_reflex_internal_init=True) + state.substate_value = 99 + assert state.get_delta() == {} + assert state.dict() == {} From 2f4678bb4fa6bbb0e0ba4621c9908ea54041b3d9 Mon Sep 17 00:00:00 2001 From: Andreas Eismann Date: Fri, 20 Feb 2026 09:54:50 +0100 Subject: [PATCH 3/5] fix: Silencing ruff. --- tests/units/test_state.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/units/test_state.py b/tests/units/test_state.py index 738eb27e276..d34b338a2fb 100644 --- a/tests/units/test_state.py +++ b/tests/units/test_state.py @@ -4473,10 +4473,14 @@ async def test_rebind_mutable_proxy(mock_app: rx.App, token: str) -> None: class NormalState(rx.State): + """This state should be serialized.""" + value: rx.Field[int] = rx.field(42) class SkippedState(NormalState): + """This state should not be serialized.""" + skipped_value: rx.Field[int] = rx.field(43) @property @@ -4485,6 +4489,8 @@ def _skip_serialization(self) -> bool: class SkippedSubState(SkippedState): + """This state should not be serialized.""" + substate_value: rx.Field[int] = rx.field(44) From 3ddee1c0a338d11957f8078008e600b26d0ede2c Mon Sep 17 00:00:00 2001 From: Andreas Eismann Date: Fri, 20 Feb 2026 09:55:46 +0100 Subject: [PATCH 4/5] fix: Adding yet another case. Don't propagate upwards. --- tests/units/test_state.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/units/test_state.py b/tests/units/test_state.py index d34b338a2fb..760b3bb4377 100644 --- a/tests/units/test_state.py +++ b/tests/units/test_state.py @@ -4539,3 +4539,12 @@ def test_substate_of_skipped_parent_is_also_skipped(): state.substate_value = 99 assert state.get_delta() == {} assert state.dict() == {} + + +def test_normal_state_unaffected_by_skipped_substate(): + """NormalState delta must not be affected by its skipped substate.""" + state = NormalState(_reflex_internal_init=True) + state.value = 99 + delta = str(state.get_delta()) + assert "99" in delta + assert "43" not in delta From 77cf0852ec269331734ffb16e94bbe7871610ec6 Mon Sep 17 00:00:00 2001 From: Andreas Eismann Date: Fri, 20 Feb 2026 10:17:55 +0100 Subject: [PATCH 5/5] fix: Pyright should be much happier now, but why? --- tests/units/test_state.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/units/test_state.py b/tests/units/test_state.py index 760b3bb4377..36d973301a5 100644 --- a/tests/units/test_state.py +++ b/tests/units/test_state.py @@ -4475,13 +4475,13 @@ async def test_rebind_mutable_proxy(mock_app: rx.App, token: str) -> None: class NormalState(rx.State): """This state should be serialized.""" - value: rx.Field[int] = rx.field(42) + value: int = 42 class SkippedState(NormalState): """This state should not be serialized.""" - skipped_value: rx.Field[int] = rx.field(43) + skipped_value: int = 43 @property def _skip_serialization(self) -> bool: @@ -4491,22 +4491,22 @@ def _skip_serialization(self) -> bool: class SkippedSubState(SkippedState): """This state should not be serialized.""" - substate_value: rx.Field[int] = rx.field(44) + substate_value: int = 44 def test_default_skip_serialization_is_false(): - state = NormalState(_reflex_internal_init=True) + state = NormalState() assert state._skip_serialization is False def test_subclass_override_is_respected(): """Subclass override must actually take effect.""" - assert SkippedState(_reflex_internal_init=True)._skip_serialization is True - assert NormalState(_reflex_internal_init=True)._skip_serialization is False + assert SkippedState()._skip_serialization is True + assert NormalState()._skip_serialization is False def test_dict_contains_value_by_default(): - state = NormalState(_reflex_internal_init=True) + state = NormalState() state_dict = str(state.dict()) assert "42" in state_dict assert "43" not in state_dict @@ -4514,12 +4514,12 @@ def test_dict_contains_value_by_default(): def test_dict_empty_when_skip_serialization(): - state = SkippedState(_reflex_internal_init=True) + state = SkippedState() assert state.dict() == {} def test_get_delta_contains_value_after_change(): - state = NormalState(_reflex_internal_init=True) + state = NormalState() state.value = 99 state_delta = str(state.get_delta()) assert "99" in state_delta @@ -4528,14 +4528,14 @@ def test_get_delta_contains_value_after_change(): def test_get_delta_empty_when_skip_serialization(): - state = SkippedState(_reflex_internal_init=True) + state = SkippedState() state.skipped_value = 99 assert state.get_delta() == {} def test_substate_of_skipped_parent_is_also_skipped(): """Substates of a skipped parent are also skipped, even without overriding _skip_serialization.""" - state = SkippedSubState(_reflex_internal_init=True) + state = SkippedSubState() state.substate_value = 99 assert state.get_delta() == {} assert state.dict() == {} @@ -4543,7 +4543,7 @@ def test_substate_of_skipped_parent_is_also_skipped(): def test_normal_state_unaffected_by_skipped_substate(): """NormalState delta must not be affected by its skipped substate.""" - state = NormalState(_reflex_internal_init=True) + state = NormalState() state.value = 99 delta = str(state.get_delta()) assert "99" in delta