|
| 1 | +```python exec |
| 2 | +import reflex as rx |
| 3 | +from pcweb.templates.docpage import definition |
| 4 | +``` |
| 5 | + |
| 6 | +# State Mixins |
| 7 | + |
| 8 | +State mixins allow you to define shared functionality that can be reused across multiple State classes. This is useful for creating reusable components, shared business logic, or common state patterns. |
| 9 | + |
| 10 | +## What are State Mixins? |
| 11 | + |
| 12 | +A state mixin is a State class marked with `mixin=True` that cannot be instantiated directly but can be inherited by other State classes. Mixins provide a way to share: |
| 13 | + |
| 14 | +- Base variables |
| 15 | +- Computed variables |
| 16 | +- Event handlers |
| 17 | +- Backend variables |
| 18 | + |
| 19 | +## Basic Mixin Definition |
| 20 | + |
| 21 | +To create a state mixin, inherit from `rx.State` and pass `mixin=True`: |
| 22 | + |
| 23 | +```python demo exec |
| 24 | +class CounterMixin(rx.State, mixin=True): |
| 25 | + count: int = 0 |
| 26 | + |
| 27 | + @rx.var |
| 28 | + def count_display(self) -> str: |
| 29 | + return f"Count: {self.count}" |
| 30 | + |
| 31 | + @rx.event |
| 32 | + def increment(self): |
| 33 | + self.count += 1 |
| 34 | + |
| 35 | +class MyState(CounterMixin, rx.State): |
| 36 | + name: str = "App" |
| 37 | + |
| 38 | +def counter_example(): |
| 39 | + return rx.vstack( |
| 40 | + rx.heading(MyState.name), |
| 41 | + rx.text(MyState.count_display), |
| 42 | + rx.button("Increment", on_click=MyState.increment), |
| 43 | + spacing="4", |
| 44 | + align="center", |
| 45 | + ) |
| 46 | +``` |
| 47 | + |
| 48 | +In this example, `MyState` automatically inherits the `count` variable, `count_display` computed variable, and `increment` event handler from `CounterMixin`. |
| 49 | + |
| 50 | +## Multiple Mixin Inheritance |
| 51 | + |
| 52 | +You can inherit from multiple mixins to combine different pieces of functionality: |
| 53 | + |
| 54 | +```python demo exec |
| 55 | +class TimestampMixin(rx.State, mixin=True): |
| 56 | + last_updated: str = "" |
| 57 | + |
| 58 | + @rx.event |
| 59 | + def update_timestamp(self): |
| 60 | + import datetime |
| 61 | + self.last_updated = datetime.datetime.now().strftime("%H:%M:%S") |
| 62 | + |
| 63 | +class LoggingMixin(rx.State, mixin=True): |
| 64 | + log_messages: list[str] = [] |
| 65 | + |
| 66 | + @rx.event |
| 67 | + def log_message(self, message: str): |
| 68 | + self.log_messages.append(message) |
| 69 | + |
| 70 | +class CombinedState(CounterMixin, TimestampMixin, LoggingMixin, rx.State): |
| 71 | + app_name: str = "Multi-Mixin App" |
| 72 | + |
| 73 | + @rx.event |
| 74 | + def increment_with_log(self): |
| 75 | + self.increment() |
| 76 | + self.update_timestamp() |
| 77 | + self.log_message(f"Count incremented to {self.count}") |
| 78 | + |
| 79 | +def multi_mixin_example(): |
| 80 | + return rx.vstack( |
| 81 | + rx.heading(CombinedState.app_name), |
| 82 | + rx.text(CombinedState.count_display), |
| 83 | + rx.text(f"Last updated: {CombinedState.last_updated}"), |
| 84 | + rx.button("Increment & Log", on_click=CombinedState.increment_with_log), |
| 85 | + rx.vstack( |
| 86 | + *[rx.text(msg) for msg in CombinedState.log_messages[-3:]] if CombinedState.log_messages else [rx.text("No logs yet")], |
| 87 | + spacing="1" |
| 88 | + ), |
| 89 | + spacing="4", |
| 90 | + align="center", |
| 91 | + ) |
| 92 | +``` |
| 93 | + |
| 94 | +## Backend Variables in Mixins |
| 95 | + |
| 96 | +Mixins can also include backend variables (prefixed with `_`) that are not sent to the client: |
| 97 | + |
| 98 | +```python demo box |
| 99 | +class DatabaseMixin(rx.State, mixin=True): |
| 100 | + _db_connection: dict = {} # Backend only |
| 101 | + user_count: int = 0 # Sent to client |
| 102 | + |
| 103 | + @rx.event |
| 104 | + def fetch_user_count(self): |
| 105 | + # Simulate database query |
| 106 | + self.user_count = len(self._db_connection.get("users", [])) |
| 107 | + |
| 108 | +class AppState(DatabaseMixin, rx.State): |
| 109 | + app_title: str = "User Management" |
| 110 | +``` |
| 111 | + |
| 112 | +Backend variables are useful for storing sensitive data, database connections, or other server-side state that shouldn't be exposed to the client. |
| 113 | + |
| 114 | +## Computed Variables in Mixins |
| 115 | + |
| 116 | +Computed variables in mixins work the same as in regular State classes: |
| 117 | + |
| 118 | +```python demo exec |
| 119 | +class FormattingMixin(rx.State, mixin=True): |
| 120 | + value: float = 0.0 |
| 121 | + |
| 122 | + @rx.var |
| 123 | + def formatted_value(self) -> str: |
| 124 | + return f"${self.value:.2f}" |
| 125 | + |
| 126 | + @rx.var |
| 127 | + def is_positive(self) -> bool: |
| 128 | + return self.value > 0 |
| 129 | + |
| 130 | +class PriceState(FormattingMixin, rx.State): |
| 131 | + product_name: str = "Widget" |
| 132 | + |
| 133 | + @rx.event |
| 134 | + def set_price(self, price: str): |
| 135 | + try: |
| 136 | + self.value = float(price) |
| 137 | + except ValueError: |
| 138 | + self.value = 0.0 |
| 139 | + |
| 140 | +def formatting_example(): |
| 141 | + return rx.vstack( |
| 142 | + rx.heading(f"Product: {PriceState.product_name}"), |
| 143 | + rx.text(f"Price: {PriceState.formatted_value}"), |
| 144 | + rx.text(f"Positive: {PriceState.is_positive}"), |
| 145 | + rx.input( |
| 146 | + placeholder="Enter price", |
| 147 | + on_blur=PriceState.set_price, |
| 148 | + ), |
| 149 | + spacing="4", |
| 150 | + align="center", |
| 151 | + ) |
| 152 | +``` |
| 153 | + |
| 154 | +## Nested Mixin Inheritance |
| 155 | + |
| 156 | +Mixins can inherit from other mixins to create hierarchical functionality: |
| 157 | + |
| 158 | +```python demo box |
| 159 | +class BaseMixin(rx.State, mixin=True): |
| 160 | + base_value: str = "base" |
| 161 | + |
| 162 | +class ExtendedMixin(BaseMixin, mixin=True): |
| 163 | + extended_value: str = "extended" |
| 164 | + |
| 165 | + @rx.var |
| 166 | + def combined_value(self) -> str: |
| 167 | + return f"{self.base_value}-{self.extended_value}" |
| 168 | + |
| 169 | +class FinalState(ExtendedMixin, rx.State): |
| 170 | + final_value: str = "final" |
| 171 | + # Inherits base_value, extended_value, and combined_value |
| 172 | +``` |
| 173 | + |
| 174 | +This pattern allows you to build complex functionality by composing simpler mixins. |
| 175 | + |
| 176 | +## Best Practices |
| 177 | + |
| 178 | +```md alert info |
| 179 | +# Mixin Design Guidelines |
| 180 | + |
| 181 | +- **Single Responsibility**: Each mixin should have a focused purpose |
| 182 | +- **Avoid Deep Inheritance**: Keep mixin hierarchies shallow for clarity |
| 183 | +- **Document Dependencies**: If mixins depend on specific variables, document them |
| 184 | +- **Test Mixins**: Create test cases for mixin functionality |
| 185 | +- **Naming Convention**: Use descriptive names ending with "Mixin" |
| 186 | +``` |
| 187 | + |
| 188 | +## Limitations |
| 189 | + |
| 190 | +```md alert warning |
| 191 | +# Important Limitations |
| 192 | + |
| 193 | +- Mixins cannot be instantiated directly - they must be inherited by concrete State classes |
| 194 | +- Variable name conflicts between mixins are resolved by method resolution order (MRO) |
| 195 | +- Mixins cannot override methods from the base State class |
| 196 | +- The `mixin=True` parameter is required when defining a mixin |
| 197 | +``` |
| 198 | + |
| 199 | +## Common Use Cases |
| 200 | + |
| 201 | +State mixins are particularly useful for: |
| 202 | + |
| 203 | +- **Authentication**: Shared login/logout functionality |
| 204 | +- **Validation**: Common form validation logic |
| 205 | +- **Logging**: Centralized logging and debugging |
| 206 | +- **API Integration**: Shared HTTP client functionality |
| 207 | +- **UI State**: Common modal, loading, or notification patterns |
| 208 | + |
| 209 | +```python demo box |
| 210 | +class AuthMixin(rx.State, mixin=True): |
| 211 | + is_authenticated: bool = False |
| 212 | + username: str = "" |
| 213 | + |
| 214 | + @rx.event |
| 215 | + def login(self, username: str): |
| 216 | + # Simplified login logic |
| 217 | + self.username = username |
| 218 | + self.is_authenticated = True |
| 219 | + |
| 220 | + @rx.event |
| 221 | + def logout(self): |
| 222 | + self.username = "" |
| 223 | + self.is_authenticated = False |
| 224 | + |
| 225 | +class DashboardState(AuthMixin, rx.State): |
| 226 | + dashboard_data: list[str] = [] |
| 227 | + |
| 228 | + @rx.var |
| 229 | + def welcome_message(self) -> str: |
| 230 | + if self.is_authenticated: |
| 231 | + return f"Welcome, {self.username}!" |
| 232 | + return "Please log in" |
| 233 | +``` |
| 234 | + |
| 235 | +By using state mixins, you can create modular, reusable state logic that keeps your application organized and reduces code duplication. |
0 commit comments