이번 교시가 끝나면 "Add User" 버튼으로 대화상자를 열고, 폼을 제출하면 테이블에 고객이 추가되며 대화상자가 자동으로 닫힙니다. 화면이 사용자의 행동에 반응하는 처음이자 가장 핵심적인 단계입니다.
이벤트 핸들러는 State 안에 정의하고 @rx.event를 붙인 메서드입니다. 상태를 바꿀 수 있는 것은 이벤트 핸들러뿐입니다.
class State(rx.State):
users: list[User] = [...]
@rx.event
def add_user(self, form_data: dict):
self.users.append(User(**form_data))self.users가 Var가 아니라 실제 리스트이므로 append, len, if, for, 외부 라이브러리까지 마음대로 씁니다.self.users를 바꾸면 Reflex가 변경을 감지해 브라우저에 보내고, 테이블과 배지가 자동으로 다시 그려집니다. 화면을 직접 고치는 코드는 없습니다.User(**form_data)는 딕셔너리를 풀어 키워드 인자로 넘깁니다. {"name": "Kim", "email": "...", "gender": "Male"} → User(name="Kim", email="...", gender="Male").강사 노트 — 3교시의 표와 대비시키세요. "화면 함수 안에서는 Var라서 len 불가, 핸들러 안에서는 진짜 값이라 len 가능." 이 대비가 잡히면 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=이 공동의 열쇠입니다. 제출하면 각 입력의 name이 키가 되어 form_data 딕셔너리가 만들어집니다. User 필드 이름(name, email, gender)과 정확히 같아야 User(**form_data)가 동작합니다.on_submit=State.add_user에는 핸들러를 클래스로, 호출하지 않고 넘깁니다. 폼이 만든 딕셔너리가 자동으로 form_data 자리에 들어갑니다.required=True는 브라우저가 제출 전에 빈칸을 막아줍니다.type="button"이 중요합니다. 폼 안의 버튼은 기본이 submit이라서, 빼먹으면 취소를 눌러도 고객이 추가됩니다.rx.flex는 display="flex"인 박스입니다. direction="column"이면 세로로, spacing은 간격입니다.custom_attrs는 Reflex가 모르는 HTML 속성을 그대로 붙일 때 씁니다. 여기서는 화면 낙독기용 레이블입니다.def add_customer_button() -> rx.Component:
return rx.dialog.root(
rx.dialog.trigger( # 누르면 대화상자가 열리는 요소
rx.button(
rx.icon("plus", size=18),
rx.text("Add User", size="2"),
),
),
rx.dialog.content( # 대화상자 본문
rx.dialog.title("Add New User"),
rx.dialog.description("Fill the form with the user's info"),
rx.form(...), # 4-2의 폼
max_width="450px",
),
)rx.icon("plus")는 Lucide 아이콘 이름을 그대로 씁니다.add_customer_button()으로 넣습니다(제목 box 다음, 같은 부모 안).브라우저 확인 — 헤더 오른쪽 끝에 파란 "+ Add User" 버튼이 생깁니다.
localhost:3000
직접 눌러 보세요Your customer directory and audience breakdown.
버튼을 누르면 배경이 어두워지며 대화상자가 뜨고, 성별은 기본값 Male로 선택되어 있습니다. 위 미리보기에서 직접 값을 입력하고 Submit을 눌러 보세요.
여기까지 하고 실행하면 문제가 하나 보입니다. 고객은 추가되는데 대화상자가 닫히지 않습니다. 대화상자의 열림/닫힘을 브라우저가 혼자 관리하고 있어서, 서버의 핸들러가 닫으라고 말할 방법이 없기 때문입니다.
해결책은 "열려 있는가"를 State로 옮기는 것입니다. 이를 제어 컴포넌트(controlled component) 패턴이라고 합니다.
class State(rx.State):
users: list[User] = [...]
add_user_dialog_open: bool = False # 1) 열림 여부를 상태로
@rx.event
def set_add_user_dialog_open(self, open_: bool): # 2) 세터를 직접 정의
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) 제출하면 닫기rx.dialog.root(
...,
open=State.add_user_dialog_open, # 상태가 열림 여부를 결정
on_open_change=State.set_add_user_dialog_open, # 사용자가 열고 닫으면 상태에 반영
)open=만 주고 on_open_change를 빼먹으면, 트리거 버튼을 눌러도 상태가 안 바뀌어 대화상자가 열리지 않습니다. 둘은 항상 짝입니다.on_open_change는 열릴 때 True, 닫힐 때(Cancel, ESC, 바깥 클릭) False를 핸들러에 넘겨줍니다.State.set_변수이름을 정의 없이 쓰는 코드가 많지만, 0.10에서는 그런 메서드가 만들어지지 않으므로 위처럼 직접 정의해야 합니다.open_인 이유는 파이썬 내장 함수 open과 겹치지 않게 하려는 관례입니다.reset_on_submit=False이지만, 대화상자가 닫히면 내용이 화면에서 제거되었다가 다시 열 때 새로 그려지므로 입력칸은 빈 상태로 다시 시작합니다.{"name": ..., "email": ..., "gender": ...}로 모읍니다.add_user(form_data)를 실행합니다.users, add_user_dialog_open)만 브라우저로 돌아옵니다.브라우저 확인 — 4-4까지 적용한 뒤 Submit을 누르면 대화상자가 닫히고, 테이블에 3번째 행이 추가되며 배지가 3으로 바뀝니다. 대화상자가 열린 채로 남아 있다면 4-4의 open=과 on_open_change=를 다시 확인합니다.
localhost:3000
직접 눌러 보세요Your customer directory and audience breakdown.
"Other"를 추가해 보세요.type="email"을 넣고, 잘못된 형식을 제출하면 어떻게 되는지 확인해 보세요.return rx.toast.error("이미 있는 고객입니다")를 돌려주세요. 성공하면 rx.toast.success(...)도 돌려 보세요.name=과 form_data, User 필드의 관계를 설명할 수 있다.새 교시·새 강의 소식 받기
이 강의에 새 교시가 더해지거나 이어지는 강의가 열리면 이메일로 알려 드려요.
R
ReflexKR
눌러 보며 배우는 파이썬과 Reflex. 모든 예제는 Reflex 0.10.0에서 직접 실행해 확인했습니다.
ReflexKR은 배너 광고 없이 무료로 운영돼요. 도움이 되셨다면 커피 한 잔으로 응원해 주세요.
© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0
질문과 답변
이 교시에서 궁금한 점을 남겨 주세요. 모든 언어 페이지의 질문이 함께 모입니다.