Manage UI State in Reflex with rx.State
Reflex binds Python server-side state to React UI components, updating the browser over WebSockets without a page reload.
14 Sept 2026, 21:40 UTC

Start with the state, not the API
Problem: you need an interactive web page—a counter, a form, or a dashboard—but you do not want to maintain a separate React frontend, a FastAPI backend, and a WebSocket layer between them.
Useful takeaway: Reflex lets you define UI components and application state in Python. When an event handler mutates an rx.State attribute, Reflex serializes the change and pushes it to the browser, where the bound React component re-renders without a full page reload.
How Reflex state works
Reflex compiles Python component declarations into a React frontend and runs Python event handlers on a FastAPI backend. The two sides communicate over WebSockets.
rx.State: a base class you subclass. Its class attributes become state variables.- Event handler: a method on the state class. Mutating
selfattributes inside it marks the state as changed. - Binding: passing a state variable or handler to a component, such as
rx.text(State.count)orrx.button(..., on_click=State.increment).
Worked example: a server-side counter
Create a new project in a terminal where you have normal user permissions:
# Run in your project directory. No sudo required.
reflex init
Then create counter.py:
import reflex as rx
class CounterState(rx.State):
count: int = 0
def increment(self):
self.count += 1
def decrement(self):
self.count -= 1
def index() -> rx.Component:
return rx.vstack(
rx.text("Count: ", CounterState.count),
rx.hstack(
rx.button("Decrement", on_click=CounterState.decrement),
rx.button("Increment", on_click=CounterState.increment),
),
)
app = rx.App()
app.add_page(index)
Run the app:
# Run in the same project directory.
reflex run
Expected check: open the local URL printed by the CLI. Click Increment. The number should change without the browser reloading the page. If it does not, check the terminal running reflex run for Python tracebacks; handler exceptions are logged server-side and do not crash the UI by default.
Risk: reflex run compiles the frontend with Node.js. If Node.js is missing or the installed version conflicts with the Reflex release, the build fails before the server starts.
Limits and common mistakes
Handlers run on the server
Event handlers execute in the backend event loop. A blocking call—such as time.sleep() or a synchronous HTTP request—stalls other users’ handlers until it returns. Offload long work to asynchronous code or background tasks instead of blocking inside a handler.
Mutate state only inside handlers
Changing a state attribute from outside a Reflex event handler may not notify the frontend. Treat rx.State instances as the single place where state changes originate.
State must be JSON-serializable
Reflex transports state between backend and frontend. Avoid storing database sessions, open file handles, large DataFrames, or custom class instances in state. Keep state small and serializable: primitives, lists, and dictionaries of JSON-compatible values.
Pin your toolchain
Frontend compilation requires Node.js and the Reflex CLI. Supported Python and Node versions change between Reflex releases. Before upgrading, compare your installed reflex, Python, and Node versions against the release notes for your target deployment environment.
Practical verification
- Run
reflex runand click the bound button. Confirm the displayed value updates without a page reload. - Raise an exception inside a handler, for example
raise ValueError("test"). Confirm the error appears in the server terminal and the browser page remains responsive. - Inspect the generated frontend build output under the project build directory to confirm React artifacts exist.
- Run
reflex --version,python --version, andnode --version, then compare them with the release notes before upgrading in production.
If any check fails, the most likely causes are a Node.js version mismatch, a non-serializable state attribute, or a blocking call inside an event handler.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.