Mình bắt đầu nghịch Reflex khi dự án automation đang ở khoảng 1500 dòng và cần một cái giao diện để theo dõi trạng thái các job. Không muốn học thêm React, không muốn setup Node.js, không muốn viết một chữ JavaScript nào. Reflex (tên cũ là Pynecone) giải quyết đúng vấn đề đó: toàn bộ frontend + backend viết bằng Python thuần, framework tự biên dịch sang React ở phía sau.
Quick Start: Dashboard chạy được trong 5 phút
Không cần cài Node.js hay bất cứ thứ gì ngoài Python. Reflex tự lo phần còn lại:
pip install reflex psutil
mkdir system-dashboard && cd system-dashboard
reflex init
Sau khi init xong, mở file system_dashboard/system_dashboard.py và thay toàn bộ nội dung bằng dashboard hiển thị CPU và RAM thực tế:
import reflex as rx
import psutil
class State(rx.State):
cpu_usage: float = 0.0
memory_usage: float = 0.0
def refresh(self):
self.cpu_usage = psutil.cpu_percent(interval=0.2)
self.memory_usage = psutil.virtual_memory().percent
def metric_card(label: str, value: str, color: str = "blue") -> rx.Component:
return rx.box(
rx.text(label, color="gray", font_size="0.85rem"),
rx.text(value, font_size="2.2rem", font_weight="bold", color=color),
padding="1.5rem",
border="1px solid #e2e8f0",
border_radius="0.75rem",
background="white",
min_width="180px",
)
def index() -> rx.Component:
return rx.vstack(
rx.heading("System Monitor", size="xl", margin_bottom="1rem"),
rx.hstack(
metric_card("CPU", f"{State.cpu_usage:.1f}%", "blue"),
metric_card("RAM", f"{State.memory_usage:.1f}%", "green"),
spacing="1rem",
),
rx.button(
"Refresh",
on_click=State.refresh,
color_scheme="blue",
margin_top="1rem",
),
padding="2rem",
align_items="start",
)
app = rx.App()
app.add_page(index, route="/", on_load=State.refresh)
app.compile()
Chạy lên:
reflex run
Mở http://localhost:3000 — bạn đã có dashboard hiển thị CPU và RAM thực tế của máy. Click “Refresh” để cập nhật. Toàn bộ viết bằng Python, không một dòng JavaScript hay HTML nào.
Reflex hoạt động như thế nào?
Cơ chế này là nguồn gốc của hầu hết lỗi khó hiểu khi mới dùng Reflex. Hiểu rõ nó, bạn tiết kiệm được ít nhất vài giờ debug.
State — nguồn sự thật duy nhất
Mọi dữ liệu cần render đều nằm trong class State(rx.State). Khi giá trị trong State thay đổi, Reflex tự động cập nhật UI qua WebSocket — không cần viết event listener hay DOM manipulation thủ công.
class DashboardState(rx.State):
items: list[dict] = [] # List → render thành table/list
is_loading: bool = False # Bool → điều kiện render spinner
filter_text: str = "" # String → input binding
# Computed var — tự cache, chỉ recalculate khi dependency đổi
@rx.var
def filtered_items(self) -> list[dict]:
if not self.filter_text:
return self.items
return [i for i in self.items if self.filter_text.lower() in i["name"].lower()]
# Async handler với intermediate state
async def load_data(self):
self.is_loading = True
yield # Render trạng thái loading ngay lập tức
import httpx
async with httpx.AsyncClient() as client:
resp = await client.get("https://api.example.com/items")
self.items = resp.json()
self.is_loading = False
Trick quan trọng: dùng yield trong async handler để Reflex render intermediate state trước khi tiếp tục xử lý. Thiếu yield, spinner sẽ không hiện — UI chỉ update sau khi toàn bộ handler chạy xong.
Components — xếp hình UI
Bộ component mặc định đủ dùng cho 90% dashboard mà không cần cài thêm gì:
- Layout:
rx.vstack,rx.hstack,rx.grid,rx.flex,rx.box - Typography:
rx.heading,rx.text,rx.markdown - Input:
rx.input,rx.select,rx.checkbox,rx.slider - Data display:
rx.table,rx.badge,rx.progress,rx.stat - Charts:
rx.recharts.*— tích hợp sẵn Recharts library
Thêm biểu đồ Line Chart theo dõi CPU real-time
Real-time chart là thứ biến dashboard từ nice-to-have thành thực sự cần thiết. Recharts tích hợp sẵn trong Reflex — không cần npm install, không cần import thêm gì. Rolling window 30 điểm đủ để bắt spike ngắn mà WebSocket vẫn nhẹ:
from datetime import datetime
class MonitorState(rx.State):
history: list[dict] = [] # [{"time": "14:30:01", "cpu": 45.2}, ...]
def record(self):
self.history.append({
"time": datetime.now().strftime("%H:%M:%S"),
"cpu": psutil.cpu_percent(interval=0.2),
})
# Giữ 30 data points gần nhất
if len(self.history) > 30:
self.history = self.history[-30:]
def cpu_chart() -> rx.Component:
return rx.recharts.line_chart(
rx.recharts.line(
data_key="cpu",
stroke="#3b82f6",
stroke_width=2,
dot=False,
),
rx.recharts.x_axis(data_key="time", tick={"fontSize": 11}),
rx.recharts.y_axis(domain=[0, 100]),
rx.recharts.tooltip(),
rx.recharts.cartesian_grid(stroke_dasharray="3 3", opacity=0.4),
data=MonitorState.history,
width="100%",
height=280,
)
Nâng cao: Auto-refresh và kết nối Database
Tự động cập nhật mỗi N giây
Background task giải quyết polling mà không cần user phải click liên tục:
import asyncio
class State(rx.State):
running: bool = False
@rx.background
async def start_monitor(self):
self.running = True
while True:
async with self:
self.record() # Cập nhật metrics
await asyncio.sleep(5) # Nghỉ 5 giây
def stop_monitor(self):
self.running = False
Kết nối SQLite với SQLModel tích hợp sẵn
Reflex đi kèm SQLModel — define model và query thẳng trong State, không cần setup ORM riêng:
import reflex as rx
from sqlmodel import Field
class Task(rx.Model, table=True):
id: int | None = Field(default=None, primary_key=True)
title: str
status: str = "pending" # pending / done
class TaskState(rx.State):
tasks: list[Task] = []
def load(self):
with rx.session() as db:
self.tasks = db.exec(Task.select()).all()
def complete_task(self, task_id: int):
with rx.session() as db:
task = db.get(Task, task_id)
task.status = "done"
db.add(task)
db.commit()
self.load() # Reload để cập nhật UI
Tips thực tế từ kinh nghiệm
Dự án automation lớn dần từ 200 lên 2000 dòng. Khi thêm Reflex dashboard vào, mình mắc đủ thứ lỗi mất vài ngày debug — ghi lại đây để bạn khỏi lặp lại:
1. Tách State theo domain, không để tất cả vào một class
State khổng lồ 50 variables là ác mộng bảo trì. Chia nhỏ theo chức năng:
# Tốt hơn nhiều
class MetricState(rx.State):
cpu: float = 0.0
memory: float = 0.0
class TaskState(rx.State):
tasks: list[dict] = []
filter: str = "all"
2. Cẩn thận với kích thước State
State được serialize qua WebSocket mỗi lần update. Lưu 10,000 rows trong State → mỗi lần bấm nút là gửi 10,000 rows đó xuống client. Giải pháp: chỉ lưu trang hiện tại, pagination server-side.
3. Dùng rx.cond thay vì Python if trong component
# Sai — Python if không reactive
def index():
if State.is_loading: # Không hoạt động như bạn nghĩ
return rx.spinner()
return rx.table(...)
# Đúng — rx.cond reactive với state
def index():
return rx.cond(
State.is_loading,
rx.spinner(),
rx.table(...),
)
4. Deploy nhanh với Reflex Cloud
# Đăng nhập và deploy — free tier có sẵn
reflex login
reflex deploy
Hoặc tự host trên VPS: chạy reflex run --env prod rồi dùng nginx reverse proxy — cổng 3000 cho frontend, 8000 cho backend.
5. Hot reload hoạt động rất mượt
Save file → browser tự cập nhật trong 1-2 giây, không cần restart server. Ngoại lệ duy nhất: thêm route mới hoặc thay đổi schema rx.Model.
Mình không dùng Reflex cho mọi thứ. SEO quan trọng hay site công khai traffic cao thì Next.js hay Nuxt vẫn là lựa chọn đúng hơn. Nhưng internal dashboard, admin panel, monitoring tool — bất cứ thứ gì cần UI nhanh từ script Python có sẵn — Reflex tiết kiệm cho mình vài ngày mỗi lần.
