Skip to content

Commit ee147f7

Browse files
Add comprehensive state mixins documentation (#1523)
* Add comprehensive state mixins documentation - Create new documentation page at docs/state/mixins.md - Explain state mixin pattern with mixin=True parameter - Include CounterMixin example from user request - Cover inheritance of vars, computed vars, and event handlers - Show multiple mixin inheritance patterns - Document backend variables in mixins - Include best practices and limitations - Follow existing reflex-web documentation patterns Co-Authored-By: [email protected] <[email protected]> * Fix VarTypeError: Use rx.cond instead of Python if with Reflex Vars - Replace Python if statement with rx.cond for conditional rendering - Use rx.foreach for iterating over log_messages Var - Use .length() method to check if list is empty - Fixes CI compilation error in documentation Co-Authored-By: [email protected] <[email protected]> * Fix syntax error: Replace Python if with rx.cond in computed var - Replace Python if statement with rx.cond in welcome_message computed variable - Prevents VarTypeError when using Reflex Vars in conditional logic - Ensures all code examples follow proper Reflex patterns Co-Authored-By: [email protected] <[email protected]> * Fix CI failures and add mixins to State Structure navigation - Change python demo box to demo exec for class definitions to fix SyntaxError - Add interactive examples for DatabaseMixin, nested mixins, and AuthMixin - Add state.mixins to State Structure section in sidebar navigation - Fixes flexdown eval() errors by using exec() for class statements Co-Authored-By: [email protected] <[email protected]> * Move mixins.md to correct State Structure directory - Move docs/state/mixins.md to docs/state_structure/mixins.md - Update navigation to use state_structure.mixins instead of state.mixins - Fixes file organization to match State Structure section Co-Authored-By: [email protected] <[email protected]> * Replace auth example with form validation - better mixin use case - Remove problematic auth mixin example that requires singleton behavior - Add comprehensive form validation mixin example with error handling - Update common use cases to emphasize form validation over authentication - Form validation is a better mixin pattern as it's naturally reusable Co-Authored-By: [email protected] <[email protected]> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: [email protected] <[email protected]>
1 parent 8ad9cd8 commit ee147f7

File tree

2 files changed

+321
-0
lines changed

2 files changed

+321
-0
lines changed

docs/state_structure/mixins.md

Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
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.cond(
86+
CombinedState.log_messages.length() > 0,
87+
rx.vstack(
88+
rx.foreach(
89+
CombinedState.log_messages[-3:],
90+
rx.text
91+
),
92+
spacing="1"
93+
),
94+
rx.text("No logs yet")
95+
),
96+
spacing="4",
97+
align="center",
98+
)
99+
```
100+
101+
## Backend Variables in Mixins
102+
103+
Mixins can also include backend variables (prefixed with `_`) that are not sent to the client:
104+
105+
```python demo exec
106+
class DatabaseMixin(rx.State, mixin=True):
107+
_db_connection: dict = {} # Backend only
108+
user_count: int = 0 # Sent to client
109+
110+
@rx.event
111+
def fetch_user_count(self):
112+
# Simulate database query
113+
self.user_count = len(self._db_connection.get("users", []))
114+
115+
class AppState(DatabaseMixin, rx.State):
116+
app_title: str = "User Management"
117+
118+
def database_example():
119+
return rx.vstack(
120+
rx.heading(AppState.app_title),
121+
rx.text(f"User count: {AppState.user_count}"),
122+
rx.button("Fetch Users", on_click=AppState.fetch_user_count),
123+
spacing="4",
124+
align="center",
125+
)
126+
```
127+
128+
Backend variables are useful for storing sensitive data, database connections, or other server-side state that shouldn't be exposed to the client.
129+
130+
## Computed Variables in Mixins
131+
132+
Computed variables in mixins work the same as in regular State classes:
133+
134+
```python demo exec
135+
class FormattingMixin(rx.State, mixin=True):
136+
value: float = 0.0
137+
138+
@rx.var
139+
def formatted_value(self) -> str:
140+
return f"${self.value:.2f}"
141+
142+
@rx.var
143+
def is_positive(self) -> bool:
144+
return self.value > 0
145+
146+
class PriceState(FormattingMixin, rx.State):
147+
product_name: str = "Widget"
148+
149+
@rx.event
150+
def set_price(self, price: str):
151+
try:
152+
self.value = float(price)
153+
except ValueError:
154+
self.value = 0.0
155+
156+
def formatting_example():
157+
return rx.vstack(
158+
rx.heading(f"Product: {PriceState.product_name}"),
159+
rx.text(f"Price: {PriceState.formatted_value}"),
160+
rx.text(f"Positive: {PriceState.is_positive}"),
161+
rx.input(
162+
placeholder="Enter price",
163+
on_blur=PriceState.set_price,
164+
),
165+
spacing="4",
166+
align="center",
167+
)
168+
```
169+
170+
## Nested Mixin Inheritance
171+
172+
Mixins can inherit from other mixins to create hierarchical functionality:
173+
174+
```python demo exec
175+
class BaseMixin(rx.State, mixin=True):
176+
base_value: str = "base"
177+
178+
class ExtendedMixin(BaseMixin, mixin=True):
179+
extended_value: str = "extended"
180+
181+
@rx.var
182+
def combined_value(self) -> str:
183+
return f"{self.base_value}-{self.extended_value}"
184+
185+
class FinalState(ExtendedMixin, rx.State):
186+
final_value: str = "final"
187+
188+
def nested_mixin_example():
189+
return rx.vstack(
190+
rx.text(f"Base: {FinalState.base_value}"),
191+
rx.text(f"Extended: {FinalState.extended_value}"),
192+
rx.text(f"Combined: {FinalState.combined_value}"),
193+
rx.text(f"Final: {FinalState.final_value}"),
194+
spacing="4",
195+
align="center",
196+
)
197+
```
198+
199+
This pattern allows you to build complex functionality by composing simpler mixins.
200+
201+
## Best Practices
202+
203+
```md alert info
204+
# Mixin Design Guidelines
205+
206+
- **Single Responsibility**: Each mixin should have a focused purpose
207+
- **Avoid Deep Inheritance**: Keep mixin hierarchies shallow for clarity
208+
- **Document Dependencies**: If mixins depend on specific variables, document them
209+
- **Test Mixins**: Create test cases for mixin functionality
210+
- **Naming Convention**: Use descriptive names ending with "Mixin"
211+
```
212+
213+
## Limitations
214+
215+
```md alert warning
216+
# Important Limitations
217+
218+
- Mixins cannot be instantiated directly - they must be inherited by concrete State classes
219+
- Variable name conflicts between mixins are resolved by method resolution order (MRO)
220+
- Mixins cannot override methods from the base State class
221+
- The `mixin=True` parameter is required when defining a mixin
222+
```
223+
224+
## Common Use Cases
225+
226+
State mixins are particularly useful for:
227+
228+
- **Form Validation**: Shared validation logic across forms
229+
- **UI State Management**: Common modal, loading, or notification patterns
230+
- **Logging**: Centralized logging and debugging
231+
- **API Integration**: Shared HTTP client functionality
232+
- **Data Formatting**: Consistent data presentation across components
233+
234+
```python demo exec
235+
class ValidationMixin(rx.State, mixin=True):
236+
errors: dict[str, str] = {}
237+
is_loading: bool = False
238+
239+
@rx.event
240+
def validate_email(self, email: str) -> bool:
241+
if "@" not in email or "." not in email:
242+
self.errors["email"] = "Invalid email format"
243+
return False
244+
self.errors.pop("email", None)
245+
return True
246+
247+
@rx.event
248+
def validate_required(self, field: str, value: str) -> bool:
249+
if not value.strip():
250+
self.errors[field] = f"{field.title()} is required"
251+
return False
252+
self.errors.pop(field, None)
253+
return True
254+
255+
@rx.event
256+
def clear_errors(self):
257+
self.errors = {}
258+
259+
class ContactFormState(ValidationMixin, rx.State):
260+
name: str = ""
261+
email: str = ""
262+
message: str = ""
263+
264+
@rx.event
265+
def submit_form(self):
266+
self.clear_errors()
267+
valid_name = self.validate_required("name", self.name)
268+
valid_email = self.validate_email(self.email)
269+
valid_message = self.validate_required("message", self.message)
270+
271+
if valid_name and valid_email and valid_message:
272+
self.is_loading = True
273+
yield rx.sleep(1)
274+
self.is_loading = False
275+
self.name = ""
276+
self.email = ""
277+
self.message = ""
278+
279+
def validation_example():
280+
return rx.vstack(
281+
rx.heading("Contact Form"),
282+
rx.input(
283+
placeholder="Name",
284+
value=ContactFormState.name,
285+
on_change=ContactFormState.set_name,
286+
),
287+
rx.cond(
288+
ContactFormState.errors.contains("name"),
289+
rx.text(ContactFormState.errors["name"], color="red"),
290+
),
291+
rx.input(
292+
placeholder="Email",
293+
value=ContactFormState.email,
294+
on_change=ContactFormState.set_email,
295+
),
296+
rx.cond(
297+
ContactFormState.errors.contains("email"),
298+
rx.text(ContactFormState.errors["email"], color="red"),
299+
),
300+
rx.text_area(
301+
placeholder="Message",
302+
value=ContactFormState.message,
303+
on_change=ContactFormState.set_message,
304+
),
305+
rx.cond(
306+
ContactFormState.errors.contains("message"),
307+
rx.text(ContactFormState.errors["message"], color="red"),
308+
),
309+
rx.button(
310+
"Submit",
311+
on_click=ContactFormState.submit_form,
312+
loading=ContactFormState.is_loading,
313+
),
314+
spacing="4",
315+
align="center",
316+
width="300px",
317+
)
318+
```
319+
320+
By using state mixins, you can create modular, reusable state logic that keeps your application organized and reduces code duplication.

pcweb/components/docpage/sidebar/sidebar_items/learn.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ def get_sidebar_items_backend():
158158
children=[
159159
state_structure.overview,
160160
state_structure.component_state,
161+
state_structure.mixins,
161162
],
162163
),
163164
create_item(

0 commit comments

Comments
 (0)