Tiempo 2:10–2:55
Al terminar esta lección, el botón "Add User" abrirá un diálogo; al enviar el formulario, el cliente se añadirá a la tabla y el diálogo se cerrará solo. Es el primer paso, y el más importante, para que la pantalla reaccione a lo que hace el usuario.
Un manejador de eventos (event handler) es un método definido dentro del State y decorado con @rx.event. Solo los manejadores de eventos pueden modificar el estado.
class State(rx.State):
users: list[User] = [...]
@rx.event
def add_user(self, form_data: dict):
self.users.append(User(**form_data))self.users no es una Var, sino una lista real, así que puedes usar libremente append, len, if, for e incluso librerías externas.self.users, Reflex detecta el cambio, lo envía al navegador y la tabla y la insignia se vuelven a dibujar solas. No hay código que modifique la pantalla directamente.User(**form_data) desempaqueta el diccionario y lo pasa como argumentos de palabra clave: {"name": "Kim", "email": "...", "gender": "Male"} → User(name="Kim", email="...", gender="Male").Nota para el instructor — Contrástalo con la tabla de la lección 3: "Dentro de una función de pantalla es una Var, así que no se puede usar len; dentro de un manejador es un valor real, así que sí". Quien entiende este contraste ya ha entendido la mitad de Reflex.
rx.form(
rx.flex(
rx.input(placeholder="User Name", name="name", required=True),
rx.input(placeholder="user@reflex.dev", name="email"),
rx.select(
["Male", "Female"],
placeholder="Select gender",
default_value="Male",
name="gender",
custom_attrs={"aria-label": "Gender"},
),
rx.flex(
rx.dialog.close(
rx.button("Cancel", type="button", variant="soft", color_scheme="gray"),
),
rx.button("Submit", type="submit"),
spacing="3",
justify="end",
),
direction="column",
spacing="4",
),
on_submit=State.add_user,
reset_on_submit=False,
)name= es la clave que lo une todo. Al enviar, el name de cada campo se convierte en una clave y así se forma el diccionario form_data. Tiene que coincidir exactamente con los nombres de los campos de User (name, email, gender) para que User(**form_data) funcione.on_submit=State.add_user se le pasa el manejador a través de la clase y sin llamarlo. El diccionario que genera el formulario se coloca automáticamente en form_data.required=True hace que el navegador impida enviar el formulario con ese campo vacío.type="button" del botón Cancel es importante. Dentro de un formulario, los botones son submit por defecto, así que si lo olvidas, el cliente se añadirá aunque pulses cancelar.rx.flex es una caja con display="flex". Con direction="column" apila en vertical, y spacing es la separación.custom_attrs sirve para añadir tal cual atributos HTML que Reflex no conoce. Aquí es una etiqueta para lectores de pantalla.def add_customer_button() -> rx.Component:
return rx.dialog.root(
rx.dialog.trigger( # el elemento que abre el diálogo al pulsarlo
rx.button(
rx.icon("plus", size=18),
rx.text("Add User", size="2"),
),
),
rx.dialog.content( # el contenido del diálogo
rx.dialog.title("Add New User"),
rx.dialog.description("Fill the form with the user's info"),
rx.form(...), # el formulario de 4-2
max_width="450px",
),
)rx.icon("plus") usa directamente los nombres de los iconos de Lucide.add_customer_button() (después del box del título, dentro del mismo padre).Compruébalo en el navegador — En el extremo derecho de la cabecera aparece un botón azul "+ Add User".
localhost:3000
PruébaloYour customer directory and audience breakdown.
Al pulsar el botón, el fondo se oscurece y aparece el diálogo, con el género Male seleccionado por defecto. Escribe tú mismo algunos valores en la vista previa de arriba y pulsa Submit.
Si lo ejecutas tal como está, verás un problema: el cliente se añade, pero el diálogo no se cierra. Esto pasa porque el navegador gestiona por su cuenta si el diálogo está abierto o cerrado, y el manejador del servidor no tiene forma de decirle que se cierre.
La solución es llevar el "¿está abierto?" al State. Esto se conoce como el patrón de componente controlado (controlled component).
class State(rx.State):
users: list[User] = [...]
add_user_dialog_open: bool = False # 1) si está abierto, como estado
@rx.event
def set_add_user_dialog_open(self, open_: bool): # 2) definimos el setter nosotros mismos
self.add_user_dialog_open = open_
@rx.event
def add_user(self, form_data: dict):
self.users.append(User(**form_data))
self.add_user_dialog_open = False # 3) se cierra al enviarrx.dialog.root(
...,
open=State.add_user_dialog_open, # el estado decide si está abierto
on_open_change=State.set_add_user_dialog_open, # cuando el usuario lo abre o cierra, se refleja en el estado
)open= y te olvidas de on_open_change, al pulsar el botón el estado no cambia y el diálogo no se abre. Los dos van siempre juntos.on_open_change pasa al manejador True al abrirse y False al cerrarse (Cancel, ESC o clic fuera).State.set_nombre_variable sin definirlo, pero en la 0.10 ese método no se genera, así que tienes que definirlo tú, como arriba.open_ por convención, para no chocar con la función integrada de Python open.reset_on_submit=False, cuando el diálogo se cierra su contenido se elimina de la pantalla y se vuelve a dibujar al abrirlo, así que los campos empiezan de nuevo vacíos.{"name": ..., "email": ..., "gender": ...}.add_user(form_data).users, add_user_dialog_open).Compruébalo en el navegador — Con todo lo de 4-4 aplicado, al pulsar Submit el diálogo se cierra, se añade una tercera fila a la tabla y la insignia cambia a 3. Si el diálogo sigue abierto, revisa el open= y el on_open_change= de 4-4.
localhost:3000
PruébaloYour customer directory and audience breakdown.
"Other" a las opciones de género.type="email" al campo de email y comprueba qué pasa si envías un formato incorrecto.return rx.toast.error("Este cliente ya existe"). Si todo va bien, prueba a devolver también rx.toast.success(...).name=, form_data y los campos de User.← Anterior
Lección 3 · State y Vars: que los datos dibujen la pantalla
Siguiente →
Lección 5 · Transformar datos y gráficos
Recibe avisos de nuevas lecciones
Te avisaremos por correo cuando este curso tenga lecciones nuevas o se abra un curso de continuación.
R
ReflexKR
Aprende Python y Reflex haciendo. Todos los ejemplos se ejecutaron y verificaron en Reflex 0.10.0.
ReflexKR es gratis y sin banners publicitarios. Si te ayudó, puedes apoyarlo con un café. ☕ Invítanos un café
Aviso de afiliados© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0
Preguntas y respuestas
Pregunta lo que quieras sobre esta lección. Aquí se reúnen las preguntas de todas las versiones de idioma de esta página.