Time 1:25–2:10
In this lesson, you'll put the customer data in State and have the table and badge render from that data. This is where you learn the most important concept in Reflex: the Var.
import dataclasses
@dataclasses.dataclass
class User:
name: str
email: str
gender: strUser(name="...", email="...", gender="...").{"name": ...}) would work too, but with a dataclass your editor catches typos in field names, and you can use dot access like user.name in your UI code.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"),
]rx.State become state vars. The values you write here are the initial values.: list[User]) are required. Reflex uses the type to decide how to send the value to the browser (as JSON) and which methods you can call on it.In Lesson 1 we said "UI functions run only once, when the app starts." So what do you get when you use State.users inside a UI function? Here's what printing it actually shows:
>>> type(State.users).__name__
'ArrayCastedVar'
>>> print(State.users.length())
reflex___state____state__tutorial_261019___tutorial_261019____state.users_rx_state_.lengthInstead of a list of customers, you get a JavaScript expression. That's a Var. When the UI function runs, there are no users and no data yet, so all it can do is leave a promise: "read this value in the browser later."
That's why some Python syntax doesn't work on Vars. Here are the errors you actually get in Reflex 0.10, and what to use instead.
| What you want | Code that fails | Error you get | The Reflex way |
|---|---|---|---|
| Count items | len(State.users) | TypeError: object of type 'ArrayCastedVar' has no len() | State.users.length() |
| Branch on a condition | "a" if State.n > 1 else "b" | VarTypeError: Cannot convert Var ... to bool | rx.cond(State.n > 1, "a", "b") |
| Loop | for u in State.users: | VarTypeError: Cannot iterate over Var ... | rx.foreach(State.users, show_user) |
| Complex transformations | Computing inside a UI function | — | Compute in an event handler or an @rx.var (computed var) |
On the other hand, comparisons and arithmetic between Vars (State.page + 1) and Vars inside f-strings (f"Total: {State.total}") work fine, because the operations themselves are translated into JavaScript expressions.
Instructor note — This table is the single most important thing in the course. Most of the errors beginners hit in Reflex come from here. On video, we recommend deliberately writing len(State.users) to show the error, asking "Why doesn't this work?", and then answering with the print output above.
Now replace the placeholder badge in the header with the real customer count.
rx.badge(
State.users.length(), # a Var instead of "2"
variant="soft",
color_scheme="gray",
radius="full",
),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",
)User, but what actually comes in is a Var pointing to a User. user.name also becomes an expression meaning "read the name of this row's customer." Dot access works because you declared the fields with a dataclass.style={"_hover": {...}} is the style applied on mouse hover. Keys that start with _, like _hover and _focus, are state selectors like CSS :hover.Add the table as the second child inside the card, below the header.
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", # horizontal scroll on narrow screens
width="100%",
border_top=f"1px solid {rx.color('slate', 4)}",
border_bottom=f"1px solid {rx.color('slate', 4)}",
padding_x="12px",
),root > header (the title row) + body (the data rows) > row > cell. These map to HTML <table>, <thead>, <tbody>, <tr> and <td>.rx.foreach(list Var, function) applies the function to each item of the list in the browser. You pass the function by name without calling it (show_user, not show_user()).for is fine, because the values are known at compile time. For example: *[rx.table.column_header_cell(h) for h in ["Name", "Email", "Gender"]]Check your browser — A table with 2 customers appears inside the card, and the badge's 2 now comes from State.users.length(). Hover over a row and its background turns light gray.
localhost:3000
Live previewYour customer directory and audience breakdown.
rx.cond to show "No customers yet" instead of the table when there are 0 customers. Hint: rx.cond(State.users.length() > 0, table, rx.text(...))len(State.users) on purpose and read the error message yourself.len/if/for on it.rx.foreach without calling it.← Previous
Lesson 2 · Drawing the screen with components
Next →
Lesson 4 · Events and forms: adding customers
Get news about new lessons
We'll email you when this course gets new lessons or a follow-up course opens.
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© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0
Questions & answers
Ask anything about this lesson. Questions from every language version of this page appear together.