R

ReflexKR

Reflex Docs

Full-Stack Python Web Development with Reflex

·

Course overview

1

Meet Reflex and set up your environment

2

Drawing the screen with components

3

State and Vars: let data draw the screen

4

Events and forms: adding customers

5

Shaping data and charts

6

Wrap-up, debugging and next steps

·

Appendix

Full-Stack Python Web Development with Reflex

Appendix

A. Full code (tutorial_261019/tutorial_261019.py)

import dataclasses
from collections import Counter

import reflex as rx


@dataclasses.dataclass
class User:
    name: str
    email: str
    gender: str


class State(rx.State):
    users: list[User] = [
        User(name="Danilo Sousa", email="danilo@example.com", gender="Male"),
        User(name="Zahra Ambessa", email="zahra@example.com", gender="Female"),
    ]
    users_for_graph: list[dict] = []
    add_user_dialog_open: bool = False

    @rx.event
    def set_add_user_dialog_open(self, open_: bool):
        self.add_user_dialog_open = open_

    @rx.event
    def add_user(self, form_data: dict):
        self.users.append(User(**form_data))
        self.transform_data()
        self.add_user_dialog_open = False

    @rx.event
    def transform_data(self):
        gender_counts = Counter(user.gender for user in self.users)

        self.users_for_graph = [
            {"name": gender_group, "value": count} for gender_group, count in gender_counts.items()
        ]


def show_user(user: User):
    return rx.table.row(
        rx.table.cell(user.name),
        rx.table.cell(user.email),
        rx.table.cell(user.gender),
        style={"_hover": {"bg": rx.color("gray", 3)}},
        align="center",
    )


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(
                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,
            ),
            max_width="450px",
        ),
        open=State.add_user_dialog_open,
        on_open_change=State.set_add_user_dialog_open,
    )


def graph():
    return rx.recharts.bar_chart(
        rx.recharts.cartesian_grid(
            horizontal=False, stroke_dasharray="3 3", stroke=rx.color("slate", 4)
        ),
        rx.recharts.x_axis(
            type_="number",
            allow_decimals=False,
            axis_line=False,
            tick_line=False,
            domain=[0, "dataMax + 1"],
        ),
        rx.recharts.y_axis(
            type_="category",
            data_key="name",
            axis_line=False,
            tick_line=False,
            width=64,
        ),
        rx.recharts.bar(
            data_key="value",
            fill=rx.color("accent", 9),
            radius=[0, 6, 6, 0],
            bar_size=28,
        ),
        data=State.users_for_graph,
        layout="vertical",
        width="100%",
        height=160,
        margin={"top": 0, "right": 16, "bottom": 0, "left": 0},
    )


def index() -> rx.Component:
    return rx.box(
        rx.box(
            rx.box(
                rx.box(
                    rx.heading("Customers", size="6", as_="h3", margin="0"),
                    rx.badge(
                        State.users.length(),
                        variant="soft",
                        color_scheme="gray",
                        radius="full",
                    ),
                    display="flex",
                    align_items="center",
                    gap="12px",
                ),
                rx.text(
                    "Your customer directory and audience breakdown.",
                    size="2",
                    color=rx.color("slate", 11),
                    margin_top="6px",
                ),
            ),
            add_customer_button(),
            display="flex",
            align_items="center",
            justify_content="space-between",
            flex_wrap="wrap",
            gap="16px",
            padding="24px",
        ),
        rx.box(
            rx.table.root(
                rx.table.header(
                    rx.table.row(
                        rx.table.column_header_cell("Name"),
                        rx.table.column_header_cell("Email"),
                        rx.table.column_header_cell("Gender"),
                    )
                ),
                rx.table.body(rx.foreach(State.users, show_user)),
                variant="ghost",
                size="3",
                width="100%",
            ),
            overflow_x="auto",
            width="100%",
            border_top=f"1px solid {rx.color('slate', 4)}",
            border_bottom=f"1px solid {rx.color('slate', 4)}",
            padding_x="12px",
        ),
        rx.box(
            rx.text("Audience breakdown", size="3", weight="bold"),
            rx.text(
                "Customers by gender",
                size="2",
                color=rx.color("slate", 11),
                margin_top="4px",
                margin_bottom="20px",
            ),
            graph(),
            padding="24px",
        ),
        width="100%",
        min_width="0",
        max_width="64em",
        margin_x="auto",
        margin_y="24px",
        border=f"1px solid {rx.color('slate', 5)}",
        border_radius="16px",
        background=rx.color("slate", 1),
        overflow="hidden",
        box_shadow="0 4px 24px rgba(0, 0, 0, 0.03)",
        style={
            "& *": {"box_sizing": "border-box"},
            "& p, & h3": {"text_align": "left"},
        },
    )


app = rx.App()
app.add_page(
    index,
    title="Customer Data App",
    description="A simple app to manage customer data.",
    on_load=State.transform_data,
)

B. Glossary

TermMeaning
Full stackDevelopment that covers both the UI (frontend) and the server (backend)
ComponentA piece of the UI. In Reflex, a single rx.xxx(...) function call
propsSettings you give a component, passed as keyword arguments
StateA class holding the app's data and event handlers. Exists on the server, per session
VarAn expression in UI code that points to a State variable. Its value is filled in by the browser
Computed varA variable declared with @rx.var that is computed from other state
Event handlerA State method decorated with @rx.event. Runs on the server in response to user actions
Controlled componentA pattern where State decides a UI element's value or state (open=, value=)
Compile timeWhen the app starts and UI functions are turned into React code
RuntimeWhile the app is running and users interact with it. This is when event handlers run
WebSocketA protocol where the browser and server keep a connection open and exchange messages in both directions
Hot reloadA feature that recompiles and applies your code automatically when you save, without restarting the server

C. Turning lessons into blog posts

If you turn each lesson into one post, you get a 6-part series.

PartExample titleLesson covered
1Build Web Apps with Just Python: Getting Started with Reflex (+ Fixing Windows Errors)Lesson 1
2Drawing a UI Without HTML: Reflex Components and StylingLesson 2
3Why len() Doesn't Work in Reflex: State and VarsLesson 3
4Adding Data with Forms and DialogsLesson 4
5Aggregating Data in Python and Drawing ChartsLesson 5
68 Errors Every Reflex Beginner Runs IntoLesson 6, 6-2
  • Rewrite the Instructor note blockquotes as regular body text, or turn them into "Tip" boxes.
  • End each post with the full code so far and a screenshot of the running app, so readers can jump in partway through.
  • Take chart screenshots after the animation finishes (wait at least 2 seconds).
  • State "Based on Reflex 0.10.0" at the top of each post. Reflex's API changes quickly, and a version label saves readers a lot of trial and error.

D. Video course production guide

  • Screen layout: VS Code on the left, the browser on the right, the terminal at the bottom. Keep the moment when saving triggers a hot reload and the page changes visible on a single screen.
  • Font size: 18pt or larger in the editor, and browser zoom at 125% or more.
  • Type code a little at a time: don't paste the finished code. Type along in the order of each lesson's code blocks, and show the page every time you save.
  • Show errors on purpose: the len() error in Lesson 3, the missing Cancel button type in Lesson 4, and the dialog that won't close are all well worth reproducing deliberately.
  • Chapter markers: add timestamps in the video description using lesson and section numbers (1-1, 1-2 …) so they map directly onto this course.
  • Pacing: the (min) marker on each section is based on a live class. An edited recording usually comes out about half as long.

← Previous

Lesson 6 · Wrap-up, debugging and next steps

Get news about new lessons

We'll email you when this course gets new lessons or a follow-up course opens.

We collect: your email address · Purpose: news about new lessons or the launch of this course · Kept until: you ask us to remove it. We never use it for anything else or share it. Reply to any notice email and we'll delete it right away.

Questions & answers

Ask anything about this lesson. Questions from every language version of this page appear together.

Shown as plain text, without links or formatting.

R

ReflexKR

Learn Python and Reflex by doing. Every example was run and verified on Reflex 0.10.0.

ReflexKR is free, with no banner ads. If it helped you, you can support it with a coffee. ☕ Buy us a coffee

Affiliate disclosure
한국어English日本語简体中文EspañolDeutsch

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

Built with Reflex