Repository: reflex-dev/reflex

Issue:
[REF-2592] Backend Vars in ComponentState are not independent
All instances of the ComponentState share the same backend vars, apparently

<sub>From [SyncLinear.com](https://synclinear.com) | [REF-2592](https://linear.app/reflex-dev/issue/REF-2592/backend-vars-in-componentstate-are-not-independent)</sub>
Backend vars inherited from State Mixins not populated
**Describe the bug**
Backend vars inherited by state mixins are not populated, we need to manually copy them

**To Reproduce**
```python

class Mixin(rx.State, mixin=True):
  _backend: int = 0

class State(Mixin, rx.State):
  pass

assert "_backend" in State.backend_vars.keys()
```


Discussion:
@masenf do you have an example for this?

This seems to work fine:
```python
import reflex as rx


class ReusableCounter(rx.ComponentState):
    count: int = 0
    _bcount: int = 0

    def increment(self):
        self.count += 1
        self._bcount += 1
        print(f"count: {self.count}, _bcount: {self._bcount}")

    def decrement(self):
        self.count -= 1
        self._bcount -= 1
        print(f"count: {self.count}, _bcount: {self._bcount}")

    @classmethod
    def get_component(cls, **props):
        return rx.hstack(
            rx.button("Decrement", on_click=cls.decrement),
            rx.text(cls.count),
            rx.button("Increment", on_click=cls.increment),
            **props,
        )


reusable_counter = ReusableCounter.create


def index() -> rx.Component:
    return rx.vstack(
        reusable_counter(),
        reusable_counter(),
        reusable_counter(),
    )


app = rx.App()
app.add_page(index)
```

Is this issue about dirty states/vars?
@benedikt-bartscher i modified your code slightly

```python
import reflex as rx


class ReusableCounter(rx.ComponentState):
    count: int = 0
    _bcount: list[int] = []

    def increment(self):
        self.count += 1
        self._bcount.append(self.count)
        print(f"count: {self.count}, _bcount: {self._bcount}")

    def decrement(self):
        self.count -= 1
        self._bcount.append(self.count)
        print(f"count: {self.count}, _bcount: {self._bcount}")

    @classmethod
    def get_component(cls, **props):
        return rx.hstack(
            rx.button("Decrement", on_click=cls.decrement),
            rx.text(cls.count),
            rx.button("Increment", on_click=cls.increment),
            **props,
        )


reusable_counter = ReusableCounter.create


def index() -> rx.Component:
    return rx.vstack(
        reusable_counter(),
        reusable_counter(),
        reusable_counter(),
    )


app = rx.App()
app.add_page(index)
```

The issue seems to be when the "default" is a mutable type and accessed directly, as opposed to assigning a new value for `self._bcount`
Thanks for your fast reply. Indeed, mutable default values are causing issues.

This can be mitigated by using pydantic's `default_factory` like this:
```python
import reflex as rx
from pydantic.v1 import PrivateAttr


class ReusableCounter(rx.ComponentState):
    count: int = 0
    _bcount: list[int] = PrivateAttr(default_factory=list)

    def increment(self):
        self.count += 1
        self._bcount.append(self.count)
        print(f"count: {self.count}, _bcount: {self._bcount}")

    def decrement(self):
        self.count -= 1
        self._bcount.append(self.count)
        print(f"count: {self.count}, _bcount: {self._bcount}")

    @classmethod
    def get_component(cls, **props):
        return rx.hstack(
            rx.button("Decrement", on_click=cls.decrement),
            rx.text(cls.count),
            rx.button("Increment", on_click=cls.increment),
            **props,
        )


reusable_counter = ReusableCounter.create


def index() -> rx.Component:
    return rx.vstack(
        reusable_counter(),
        reusable_counter(),
        reusable_counter(),
    )


app = rx.App()
app.add_page(index)
```

However, it would be nice if reflex would copy those mutable defaults by default to prevent crazy issues





Patch:
diff --git a/reflex/state.py b/reflex/state.py
index 73ebc08921b..86bb91d173d 100644
--- a/reflex/state.py
+++ b/reflex/state.py
@@ -200,6 +200,7 @@ async def _no_chain_background_task_gen(*args, **kwargs):
 
 
 RESERVED_BACKEND_VAR_NAMES = {
+    "_abc_impl",
     "_backend_vars",
     "_was_touched",
 }
@@ -558,6 +559,14 @@ def __init_subclass__(cls, mixin: bool = False, **kwargs):
                     cls.computed_vars[newcv._var_name] = newcv
                     cls.vars[newcv._var_name] = newcv
                     continue
+                if (
+                    types.is_backend_variable(name, cls)
+                    and name not in RESERVED_BACKEND_VAR_NAMES
+                    and name not in cls.inherited_backend_vars
+                    and not isinstance(value, FunctionType)
+                ):
+                    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):


Test patch:
diff --git a/tests/test_state.py b/tests/test_state.py
index 8b91f1cfe5b..0a72282567d 100644
--- a/tests/test_state.py
+++ b/tests/test_state.py
@@ -2969,3 +2969,23 @@ def test_redis_state_manager_config_knobs(tmp_path, expiration_kwargs, expected_
         state_manager = StateManager.create(state=State)
         assert state_manager.lock_expiration == expected_values[0]  # type: ignore
         assert state_manager.token_expiration == expected_values[1]  # type: ignore
+
+
+class MixinState(State, mixin=True):
+    """A mixin state for testing."""
+
+    num: int = 0
+    _backend: int = 0
+
+
+class UsesMixinState(MixinState, State):
+    """A state that uses the mixin state."""
+
+    pass
+
+
+def test_mixin_state() -> None:
+    """Test that a mixin state works correctly."""
+    assert "num" in UsesMixinState.base_vars
+    assert "num" in UsesMixinState.vars
+    assert UsesMixinState.backend_vars == {"_backend": 0}