R

ReflexKR

Documentación de Reflex

Desarrollo web full stack con Python y Reflex

·

Resumen del curso

1

Conoce Reflex y prepara tu entorno

2

Dibujar la pantalla con componentes

3

State y Vars: que los datos dibujen la pantalla

4

Eventos y formularios: añadir clientes

5

Transformar datos y gráficos

6

Resumen, depuración y próximos pasos

·

Apéndice

Desarrollo web full stack con Python y Reflex
Lección 4

Tiempo 2:10–2:55

Eventos y formularios: añadir clientes

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.

4-1. Manejadores de eventos (10 min)

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))
  • Los manejadores se ejecutan en Python de verdad, en el servidor. Aquí self.users no es una Var, sino una lista real, así que puedes usar libremente append, len, if, for e incluso librerías externas.
  • Cuando cambias 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.

4-2. Crear el formulario (10 min)

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.
  • A 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.
  • El 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.

4-3. Envolverlo en un diálogo (10 min)

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.
  • La función que acabas de crear se coloca en la parte derecha de la cabecera como add_customer_button() (después del box del título, dentro del mismo padre).
  • Dividir trozos de pantalla en funciones es igual que separar funciones en Python normal. No hace falta ninguna sintaxis especial.

Compruébalo en el navegador — En el extremo derecho de la cabecera aparece un botón azul "+ Add User".

localhost:3000

Pruébalo

Customers

2

Your customer directory and audience breakdown.

NameEmailGender
Danilo Sousadanilo@example.comMale
Zahra Ambessazahra@example.comFemale
Paso 4-3: pulsa Add User y añade un cliente. Se añade, pero el diálogo no se cierra.

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.

4-4. Controlar el diálogo con el estado (10 min)

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 enviar
rx.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
)
  • Si pones solo 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).
  • Reflex 0.10 no tiene setters automáticos. En materiales antiguos hay mucho código que usa 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.
  • El parámetro se llama open_ por convención, para no chocar con la función integrada de Python open.
  • Aunque 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.

4-5. El camino que recorre un clic (5 min)

  1. El usuario pulsa Submit.
  2. El navegador reúne los valores introducidos en {"name": ..., "email": ..., "gender": ...}.
  3. Ese valor viaja al servidor por WebSocket.
  4. El servidor busca el State de ese usuario y ejecuta add_user(form_data).
  5. Al navegador vuelven solo las variables que han cambiado (users, add_user_dialog_open).
  6. La tabla y la insignia se vuelven a dibujar y el diálogo se cierra.

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ébalo

Customers

2

Your customer directory and audience breakdown.

NameEmailGender
Danilo Sousadanilo@example.comMale
Zahra Ambessazahra@example.comFemale
Lección 4 completada: al enviar, el diálogo se cierra y el número de la insignia aumenta.

Práctica

  1. Añade "Other" a las opciones de género.
  2. Añade type="email" al campo de email y comprueba qué pasa si envías un formato incorrecto.
  3. En el manejador, si el nombre ya existe, no lo añadas y devuelve return rx.toast.error("Este cliente ya existe"). Si todo va bien, prueba a devolver también rx.toast.success(...).

Punto de control

  • Al añadir un cliente con Add User, la tabla y la insignia cambian.
  • Después de enviar, el diálogo se cierra automáticamente.
  • Al pulsar Cancel, no se añade ningún cliente.
  • Puedes explicar la relación entre 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.

Datos: tu correo electrónico · Finalidad: avisos de nuevas lecciones o de la publicación de este curso · Conservación: hasta que pidas que lo borremos. No lo usamos para nada más ni lo compartimos. Responde a cualquier aviso y lo borraremos enseguida.

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.

Se muestra como texto plano, sin enlaces ni formato.

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
한국어English日本語简体中文EspañolDeutsch

© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0

Built with Reflex