R

ReflexKR

Reflex Docs

Reflex Basics, Hands-On

·

Course overview

1

Click and it changes: State and events

2

Now you see it: conditional rendering

3

Drawing lists: rx.foreach

4

Why doesn't if work? Compile time vs. runtime

5

Making it look good: components, props and style

6

Apps, pages and a fruit basket

Reflex Basics, Hands-On
Lesson 1

Time 0:00–0:08

Click and it changes: State and events

Before any explanation, let's click something. Press the button below and the number goes up by 1. By the end of this lesson, you'll be able to explain exactly how these dozen or so lines of code work.

import reflex as rx


class CounterState(rx.State):
    count: int = 0

    @rx.event
    def increment(self):
        self.count += 1


def counter_increment():
    return rx.hstack(
        rx.heading(CounterState.count, as_="h2"),
        rx.button("Increment", on_click=CounterState.increment),
    )

localhost:3000

Try it

0

Click Increment. The number goes up by 1.

The code has three parts.

PartCodeWhat it does
Stateclass CounterState(rx.State)A box that holds the data that changes (count)
Event handlerdef increment(self)A function that changes the data
Event triggeron_click=CounterState.incrementThe link that says "call this function on click"

When you click the button, increment runs on the server. Once count changes, Reflex automatically redraws just the parts of the page that use it. You never write a single line of code that updates the page directly.

1-1. State: where changing data lives (2 min)

Apps have data that changes over time: the number in a counter, whether you're logged in, the items in a shopping cart. In Reflex, you keep this kind of data in a State class.

  • Create a class that inherits from rx.State and declare variables inside it. These variables are called Vars (state vars).
  • Every Var needs a type annotation (count: int), and you can give it a default value (= 0).

To use a Var in your UI, refer to it through the class name, like MyState.count. You can pass it as a child or as a prop value.

class MyState(rx.State):
    count: int = 0
    color: str = "red"


def counter():
    return rx.hstack(
        # The heading `color` prop is set to the `color` var in MyState.
        rx.heading("Count: ", as_="h2", color=MyState.color),
        # The `count` var in `MyState` is passed as a child to the heading component.
        rx.heading(MyState.count, as_="h2"),
    )

localhost:3000

Live preview

Count:

0

The color Var is used as the text color prop, and the count Var as a child.

You can use the same Var in as many components as you like. When its value changes, every place that uses it updates together.

Good to know — Only event handlers can change State. You can't assign a value directly in UI code, as in MyState.count = 5.

1-2. Event handlers and @rx.event (2 min)

Methods on a State that change its data are called event handlers. Special props on components, such as on_click, are called event triggers. Connect a trigger to a handler, and your page responds to what the user does.

  1. The user clicks a button (an event trigger fires).
  2. The connected event handler runs on the server and changes the State.
  3. Reflex sends the updated State to the browser, and the page is redrawn.

We recommend always putting @rx.event above your handlers. With this decorator, your editor and type checker can verify how many arguments a handler takes and of what types, so you catch mistakes early.

1-3. Event handlers with arguments (2 min)

Like regular functions, handlers can take arguments. Let's build an "add 1" and an "add 5" button with a single handler.

class CounterState2(rx.State):
    count: int = 0

    @rx.event
    def increment(self, amount: int):
        self.count += amount


def counter_variable():
    return rx.hstack(
        rx.heading(CounterState2.count, as_="h2"),
        rx.button("Increment by 1", on_click=lambda: CounterState2.increment(1)),
        rx.button("Increment by 5", on_click=lambda: CounterState2.increment(5)),
    )

localhost:3000

Try it

0

Both buttons call the same handler with different values.
  • This example wraps the call in a lambda, as in lambda: CounterState2.increment(1), to make the intent clear: "on click, call it with 1."
  • Writing on_click=CounterState2.increment(1) directly works exactly the same way. It doesn't run the handler right away; it only creates the event information "on click, run this with amount=1." The handler code runs on the server when the button is clicked.

1-4. When the trigger passes a value: on_blur (2 min)

on_click doesn't pass any value to its handler, but some triggers do. on_blur fires when the input loses focus (when you click somewhere else or press Tab), and it passes the text you typed to the handler.

class TextState(rx.State):
    text: str = ""

    @rx.event
    def update_text(self, new_text: str):
        self.text = new_text


def text_input():
    return rx.vstack(
        rx.heading(TextState.text, as_="h2"),
        rx.input(default_value=TextState.text, on_blur=TextState.update_text),
    )

localhost:3000

Try it

Type something in the input, then click outside it. The heading above updates.

Good to know — A handler must take as many arguments as its trigger passes. on_blur passes one value, so update_text takes one argument, new_text. If the numbers don't match, you get an error.

Exercises

  1. Add a decrement handler to CounterState and make a button that lowers the number by 1.
  2. Add a "Reset" button to counter_variable that sets count back to 0. Could you do it with a handler that takes an argument?
  3. Try on_change instead of on_blur. Now the heading updates with every keystroke.

Checkpoint

  • You can explain what State, a Var, an event handler and an event trigger each are.
  • You can refer to a Var in your UI as ClassName.var_name.
  • You know why passing an argument in on_click doesn't run the handler right away.

← Previous

Course overview

Next →

Lesson 2 · Now you see it: conditional rendering

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