Reflex Core Developer
This skill covers the core technical aspects of building full-stack web applications with Reflex — a Python framework that compiles to a React frontend and FastAPI backend. It includes state management, components, event handlers, styling, routing, database integration, logging, testing, and best practices.
For visual design and aesthetics → see skill reflex-design.
For custom FastAPI endpoints → see skill reflex-fastapi.
Architecture Overview
Reflex is a full-stack Python framework for building web applications without writing JavaScript. Apps compile to a React frontend and FastAPI backend, with state management and event handlers running entirely in Python.
- Frontend: Compiled to React (JavaScript) for UI rendering
- Backend: FastAPI server running Python event handlers
- Communication: WebSockets for real-time state updates
- State: Server-side Python state synchronized to frontend
Project Structure
my_app/
├── my_app/
│ ├── __init__.py
│ ├── my_app.py # App entry point and route registration
│ ├── states/
│ │ ├── auth_state.py # Authentication state
│ │ ├── data_state.py # Business data state
│ │ └── ui_state.py # UI state (modals, loading, etc.)
│ ├── components/
│ │ ├── navbar.py
│ │ └── shared.py # Reusable components
│ ├── pages/
│ │ ├── index.py
│ │ └── dashboard.py
│ ├── api/
│ │ ├── routes.py # FastAPI custom endpoints
│ │ └── schemas.py # Pydantic V2 models
│ ├── exceptions.py # Custom exception classes
│ └── logging_config.py # Logging configuration
├── tests/
│ ├── test_states.py
│ └── test_api.py
├── docs/
│ ├── README.md
│ └── CHANGELOG.md
├── assets/
├── .web/ # Auto-generated, do not edit
├── .env # Environment variables (never in git)
├── rxconfig.py
├── version.py # __version__ = "1.0.0"
└── requirements.txt
Core Concepts
State Management
State is a Python class that holds all mutable data and event handlers. All state variables must be JSON-serializable.
import reflex as rx
class AppState(rx.State):
# State variables (any JSON-serializable type)
count: int = 0
items: list[str] = []
user_name: str = ""
# Event handlers - the ONLY way to modify state
def increment(self):
self.count += 1
def add_item(self, item: str):
self.items.append(item)
# Computed vars (derived state)
@rx.var
def item_count(self) -> int:
return len(self.items)
Key Rules:
- State vars MUST be JSON-serializable (int, str, list, dict, bool, float)
- Only event handlers can modify state
- Use
@rx.vardecorator for computed/derived values - State is per-user session (isolated between users)
- Split large states into substates (
AuthState,DataState,UIState)
Components
Components are UI building blocks. Reflex provides 60+ built-in components.
import reflex as rx
def header() -> rx.Component:
return rx.heading("My App", size="lg")
def counter_component(state: AppState) -> rx.Component:
return rx.vstack(
rx.text(f"Count: {state.count}"),
rx.button("Increment",
spacing="4"
)
Common Components:
- Layout:
rx.vstack,rx.hstack,rx.box,rx.container - Text:
rx.heading,rx.text,rx.code - Input:
rx.input,rx.text_area,rx.select,rx.checkbox - Interactive:
rx.button,rx.link,rx.icon_button - Data:
rx.table,rx.data_table,rx.list - Charts:
rx.recharts.line_chart,rx.recharts.bar_chart, etc.
Event Handlers
Event handlers respond to user interactions and are the ONLY way to modify state.
class FormState(rx.State):
form_data: dict[str, str] = {}
# Simple event handler
def handle_submit(self):
print(f"Submitted: {self.form_data}")
# Event handler with argument
def update_field(self, field: str, value: str):
self.form_data[field] = value
# Async event handler (for API calls, DB queries)
async def fetch_data(self):
import httpx
async with httpx.AsyncClient() as client:
response = await client.get("https://api.example.com/data")
self.data = response.json()
Event Triggers (connect components to handlers):
on_click: Button clickson_change: Input field changeson_submit: Form submissionson_mount: Component first renderson_blur,on_focus: Input focus events
Main App File Pattern
import reflex as rx
# 1. Define State
class State(rx.State):
count: int = 0
def increment(self):
self.count += 1
# 2. Define Pages
def index() -> rx.Component:
return rx.container(
rx.heading("Welcome"),
rx.button("Click",
rx.text(f"Count: {State.count}")
)
def about() -> rx.Component:
return rx.container(
rx.heading("About"),
rx.link("Home", href="/")
)
# 3. Create App and Add Routes
app = rx.App()
app.add_page(index, route="/")
app.add_page(about, route="/about")
Common Patterns
Form Handling
class FormState(rx.State):
name: str = ""
email: str = ""
def handle_submit(self, form_data: dict):
self.name = form_data.get("name", "")
self.email = form_data.get("email", "")
def form_page():
return rx.form(
rx.vstack(
rx.input(name="name", placeholder="Name"),
rx.input(name="email", placeholder="Email"),
rx.button("Submit", type="submit"),
),
)
Data Tables
class DataState(rx.State):
data: list[dict] = [
{"id": 1, "name": "Alice", "age": 25},
{"id": 2, "name": "Bob", "age": 30},
]
def data_table_page():
return rx.data_table(
data=DataState.data,
columns=["id", "name", "age"],
sort=True,
search=True,
pagination=True,
)
File Upload
class UploadState(rx.State):
async def handle_upload(self, files: list[rx.UploadFile]):
for file in files:
upload_data = await file.read()
outfile = f"./uploads/{file.filename}"
with open(outfile, "wb") as f:
f.write(upload_data)
def upload_page():
return rx.vstack(
rx.upload(
rx.button("Select Files"),
id="upload1",
),
rx.button(
"Upload",
),
)
Database Integration (DuckDB)
import duckdb
import polars as pl
class DBState(rx.State):
records: list[dict] = []
async def load_data(self):
conn = duckdb.connect("data/mydb.duckdb")
df = conn.execute("SELECT * FROM mytable").pl()
self.records = df.to_dicts()
conn.close()
async def insert_record(self, data: dict):
conn = duckdb.connect("data/mydb.duckdb")
conn.execute(
"INSERT INTO mytable (name, value) VALUES (?, ?)",
[data["name"], data["value"]]
)
conn.close()
await self.load_data() # Refresh
Styling & Layout
Inline Styling
rx.box(
rx.text("Styled text"),
bg="#1a5f9e",
color="white",
padding="4",
border_radius="md",
)
Responsive Layout
rx.container(
rx.responsive_grid(
rx.box("Item 1", bg="blue"),
rx.box("Item 2", bg="green"),
rx.box("Item 3", bg="red"),
columns=[1, 2, 3], # 1 col mobile, 2 tablet, 3 desktop
spacing="4",
),
max_width="1200px",
)
Common Style Props
- Layout:
width,height,padding,margin,display - Colors:
bg(background),color(text) - Typography:
font_size,font_weight,text_align - Borders:
border,border_radius,border_color - Spacing:
spacing(for stacks),gap
Routing
app = rx.App()
# Route with parameters
@rx.page(route="/user/[id]")
def user_page() -> rx.Component:
return rx.text(f"User ID: {State.router.page.params.get('id')}")
# Simple routes
app.add_page(index, route="/")
app.add_page(about, route="/about")
# Links
rx.link("Go to About", href="/about")
# Programmatic navigation
def go_home(self):
return rx.redirect("/")
Best Practices
State Organization: Split large states into substates
class AuthState(rx.State): user: str = "" class DataState(rx.State): items: list = []Component Reusability: Create reusable component functions
def card(title: str, content: str) -> rx.Component: return rx.box( rx.heading(title, size="md"), rx.text(content), padding="4", border="1px solid #ddd", )Event Handler Performance: Use async for I/O operations
async def fetch_data(self): # Async I/O won't block other users self.data = await some_api_call()Type Hints: Always type-hint state vars and event handlers
count: int = 0 items: list[str] = [] def update_count(self, value: int) -> None: self.count = valueDesign Intentionality: Every component should reflect the chosen aesthetic — padding, color, font, and motion must be cohesive. Use
rx.html.stylefor global CSS variables and keyframe animations. See skillreflex-designfor full guidelines.FastAPI Scope: Use custom FastAPI endpoints only for logic that lives outside Reflex state (webhooks, external integrations, JWT auth). Everything else belongs in event handlers. See skill
reflex-fastapifor endpoint patterns.
Logging
Never use print(). Configure structured logging at module level.
import logging
# Global setup — place in logging_config.py and import at app startup
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
)
# In each module
logger = logging.getLogger(__name__)
class DataState(rx.State):
async def load_data(self):
try:
logger.info("Starting data load")
# ... logic
logger.debug("Data loaded: %d records", len(self.records))
except Exception as e:
logger.error("Error loading data: %s", e, exc_info=True)
raise
Recommended levels:
DEBUG: internal flow and intermediate values (development only)INFO: relevant normal operations (process start/end)WARNING: unexpected non-critical situationsERROR: recoverable failures with full contextCRITICAL: failures that halt the application
Testing
State Test (pytest-asyncio)
import pytest
from my_app.states.data_state import DataState
@pytest.mark.asyncio
async def test_load_data_success():
state = DataState()
await state.load_data()
assert isinstance(state.records, list)
@pytest.mark.asyncio
async def test_load_data_empty_returns_list():
state = DataState()
state.records = []
assert len(state.records) == 0
Development Workflow
pip install reflex
reflex init
reflex run # Dev server at http://localhost:3000
reflex export # Production build
reflex db init # Initialize database (if using Reflex DB)
reflex db migrate # Run migrations
References
- Reflex Docs: https://reflex.dev/docs/getting-started/introduction/
- Reflex Component Library: https://reflex.dev/docs/library
- Reflex Tutorials: https://reflex.dev/docs/getting-started/tutorial/
- Example Apps: See
examples/directory for complete working examples - Common Patterns Reference: See
references/patterns.mdfor authentication flows, real-time updates, complex form validation, multi-step workflows, and data visualization