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.
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:
ClientStateVar integrates seamlessly into Reflex components and can optionally be made globally accessible across your entire application.
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)ClientStateVar provides several methods for different use cases:
Method | Description | Usage Context |
---|---|---|
| Access current value in component render | Frontend rendering only |
| Set value with specific data | Frontend event handlers |
| Returns event handler that updates value | Frontend event triggers |
| Pull client value into backend handler | Backend event handlers |
| Push value from backend to client | Backend event handlers |
.value
, .set_value()
, and .set
work only in frontend contexts.retrieve()
and .push()
require global_ref=True
and work in backend contexts.retrieve()
to get client state values in backend event handlers.push()
to update client state from backend event handlersHere's a basic implementation showing tab switching functionality:
import reflex as rx from reflex.experimental import ClientStateVar # Create the client state variable ActiveTab = ClientStateVar.create("tab_v1", 0) TabList = ["GitHub", "Twitter", "YouTube"] def tab_navigation(): 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.value
renders 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 stateHere'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, name: 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 {session.blocks.length()}", class_name="text-sm font-light italic", ), class_name="flex flex-row justify-between items-center p-4 border rounded-lg", )
content_editable=EditSessionName.value
toggles edit modeon_blur
# 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)
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}")
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.
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)
is_visible = ClientStateVar.create("visibility", False) # 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 = ClientStateVar.create("form", {}) rx.input( value=form_state.value.get("username", ""), on_change=lambda v: form_state.set_value({ **form_state.value, "username": v }) )
show_details = ClientStateVar.create("show_details", False) 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") ) )
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.
.retrieve()
and .push()
for backend integration when neededHome
Pantry UI