Introduction
ClientStateVar is Reflex's solution for frontend-only state management, modeled after React's familiar useState hook. It enables responsive UI updates directly in the browser without requiring a round trip to the backend server. This makes ClientStateVar perfect for interactive elements like active tabs, toggles, form inputs, modals, and any UI state that doesn't need server persistence.
What is ClientStateVar?
Unlike regular Reflex State variables which are backend-driven and persistent across sessions, ClientStateVar exists only in the browser's memory and updates instantly. This separation enables:
- Instant UI Updates: No network latency for immediate visual feedback
- Reduced Server Load: Client-side state doesn't hit your backend
- Better UX: Snappy interactions that feel native to modern web apps
- Stateless Logic: Clean separation between client UI state and server business logic
ClientStateVar integrates seamlessly into Reflex components and can optionally be made globally accessible across your entire application.
Creating Your First ClientStateVar
To create a ClientStateVar, import it from reflex.experimental and use the create method:
import reflex as rx
from reflex.experimental import ClientStateVar
# Create a simple counter
counter = ClientStateVar.create("counter", 0)
# Create a tab selector with default first tab
active_tab = ClientStateVar.create("active_tab", 0)
# Create a toggle state
is_open = ClientStateVar.create("modal_open", False)var_name: Unique identifier for the variable (optional, auto-generated if not provided)default: Initial value for the state variableglobal_ref: Whether the state should be accessible across components (default: True)
API Reference & Methods
ClientStateVar provides several methods for different use cases:
| Method | Description | Usage Context |
|---|---|---|
.value | Access current value in component render | Frontend rendering only |
.set_value(v) | Set value with specific data | Frontend event handlers |
.set | Returns event handler that updates value | Frontend event triggers |
.retrieve(callback) | Pull client value into backend handler | Backend event handlers |
.push(value) | Push value from backend to client | Backend event handlers |
.value,.set_value(), and.setwork only in frontend contexts.retrieve()and.push()requireglobal_ref=Trueand work in backend contexts- Use
.retrieve()to get client state values in backend event handlers - Use
.push()to update client state from backend event handlers
Simple Example: Tab Navigation
Here's a basic implementation showing tab switching functionality:
def tab_navigation_example():
# Create the client state variable
ActiveTab = ClientStateVar.create("tab_v1", 0)
TabList = ["GitHub", "Twitter", "YouTube"]
return rx.box(
rx.hstack(
rx.foreach(
TabList,
lambda tab, i: rx.button(
rx.text(
tab,
color=rx.cond(
ActiveTab.value == i,
rx.color("slate", 12),
rx.color("slate", 10),
),
),
on_click=ActiveTab.set_value(i),
background=rx.cond(
ActiveTab.value == i,
rx.color("gray", 3),
"transparent",
),
border_radius=rx.cond(ActiveTab.value == i, "0.375rem", "0.5rem"),
padding="0.5rem 0.75rem",
cursor="pointer",
),
),
style={
"display": "inline-flex",
"height": "2.125rem",
"align_items": "baseline",
"border_radius": "0.5rem",
"padding": "0.25rem",
"border": f"1.25px dashed {rx.color('gray', 4)}",
},
)
)ActiveTab.valuerenders the current selected tab indexActiveTab.set_value(i)creates an event handler that updates the active tabrx.cond()conditionally applies styles based on the current state
Complex Example: Session Management
Here's an advanced example showing editable session names with backend integration:
from reflex.experimental import ClientStateVar
import reflex as rx
# Client state for edit mode
EditSessionName = ClientStateVar.create("EditSessionName", False)
class SessionState(rx.State):
sessions: list[Session] = []
_id: str
def change_name(self, title: str):
self.sessions = [
(
session
if session.session_id != self._id
else session.copy(update={"name": name})
)
for session in self.sessions
]
def change_session_name(self, session_id: str):
self._id = session_id
# Use retrieve to get the edited name from the client
yield rx.call_script(
f'''
function getNewName() {{
var name = document
.getElementById("{session_id}")
.innerText;
return name;
}}
getNewName();
''',
SessionState.change_name,
)
def create_session(session: Session, index: int):
return rx.div(
rx.label(
session.name,
id=session.session_id,
class_name="text-sm font-medium w-[90px] truncate cursor-pointer",
content_editable=EditSessionName.value,
spell_check=False,
# Double-click to enable editing
on_double_click=EditSessionName.set_value(True),
# Exit edit mode and save changes
on_blur=[
EditSessionName.set_value(False),
SessionState.change_session_name(session.session_id),
],
),
rx.label(
f"Total Blocks {len(session.blocks)}",
class_name="text-sm font-light italic",
),
class_name="flex flex-row justify-between items-center p-4 border rounded-lg",
)- Client-Server Integration: EditSessionName controls UI state while SessionState handles data persistence
- Conditional Rendering:
content_editable=EditSessionName.valuetoggles edit mode - Event Chaining: Multiple actions triggered on
on_blur - Backend Communication: Using custom JavaScript to retrieve edited content
Best Practices & Patterns
When to Use ClientStateVar
- UI State: Active tabs, modal visibility, form input states
- Temporary Data: Shopping cart items, form drafts, client-side filters
- Interactive Elements: Hover states, dropdown selections, toggle switches
- Performance Critical: Frequent updates that don't need server persistence
When NOT to Use ClientStateVar
- Persistent Data: User settings, saved preferences, database records
- Security Sensitive: Authentication tokens, user permissions
- Cross-Session Data: Data that needs to survive page refreshes
- Server Logic: Business rules, validation, data processing
Global vs Local State
# Global state - accessible across components
global_state = ClientStateVar.create("global_counter", 0, global_ref=True)
# Local state - only accessible in current component tree
local_state = ClientStateVar.create("local_toggle", False, global_ref=False)Error Handling
Always wrap backend interactions with proper error handling:
def safe_retrieve_handler(self):
try:
yield my_client_state.retrieve(self.process_client_value)
except Exception as e:
yield rx.toast(f"Error retrieving client state: {e}")Advanced Usage: Backend Integration
Retrieving Client State in Backend
Use .retrieve() to pull client-side values into backend event handlers:
class MyState(rx.State):
def process_form(self):
# Get current client state value
yield form_data.retrieve(self.save_form_data)
def save_form_data(self, client_value):
# Process the retrieved value
print(f"Received from client: {client_value}")
# Save to database, validate, etc.Pushing Updates from Backend
Use .push() to update client state from backend:
class MyState(rx.State):
def reset_form(self):
# Clear client-side form state
yield form_data.push({})
yield rx.toast("Form cleared!")
def load_saved_data(self):
# Load data and push to client
saved_data = self.load_from_database()
yield form_data.push(saved_data)Common Patterns
Toggle Pattern
def toggle_pattern_example():
is_visible = ClientStateVar.create("visibility", False)
return rx.fragment(
# Toggle with current value
rx.button("Toggle", on_click=is_visible.set_value(~is_visible.value)),
# Or use a more explicit approach
rx.button("Show", on_click=is_visible.set_value(True)),
rx.button("Hide", on_click=is_visible.set_value(False)),
)Form State Pattern
def form_state_pattern_example():
form_state = ClientStateVar.create("form", {})
return rx.input(
value=form_state.value.get("username", ""),
on_change=lambda v: form_state.set_value({**form_state.value, "username": v}),
)Conditional Rendering Pattern
def conditional_rendering_pattern_example():
show_details = ClientStateVar.create("show_details", False)
return rx.box(
rx.button(
"Toggle Details", on_click=show_details.set_value(~show_details.value)
),
rx.cond(
show_details.value,
rx.div("Detailed information here..."),
rx.div("Click to show details"),
),
)Final Thoughts
ClientStateVar bridges the gap between Reflex's backend-focused architecture and modern frontend interactivity needs. By understanding when and how to use client-side state, you can build responsive, performant applications that feel native to users while maintaining clean separation between UI logic and business logic.
- Use ClientStateVar for ephemeral UI state that doesn't need server persistence
- Leverage
.retrieve()and.push()for backend integration when needed - Consider performance implications and choose between global and local state appropriately
- Follow React patterns and conventions for familiar developer experience