Reflex Logo

Intro

Gallery

Hosting

Components

New

Learn

Components

API Reference

Onboarding

Substates

/

Component-state

New in version 0.4.6.

Defining a subclass of rx.ComponentState creates a special type of state that is tied to an instance of a component, rather than existing globally in the app. A Component State combines UI code with state Vars and Event Handlers , and is useful for creating reusable components which operate independently of each other.

0

0

0

class ReusableCounter(rx.ComponentState):
    count: int = 0

    def increment(self):
        self.count += 1

    def decrement(self):
        self.count -= 1

    @classmethod
    def get_component(cls, **props):
        return rx.hstack(
            rx.button("Decrement", on_click=cls.decrement),
            rx.text(cls.count),
            rx.button("Increment", on_click=cls.increment),
            **props,
        )


reusable_counter = ReusableCounter.create


def multiple_counters():
    return rx.vstack(
        reusable_counter(),
        reusable_counter(),
        reusable_counter(),
    )

The vars and event handlers defined on the ReusableCounter class are treated similarly to a normal State class, but will be scoped to the component instance. Each time a reusable_counter is created, a new state class for that instance of the component is also created.

The get_component classmethod is used to define the UI for the component and link it up to the State, which is accessed via the cls argument. Other states may also be referenced by the returned component, but cls will always be the instance of the ComponentState that is unique to the component being returned.

Similar to a normal Component, the ComponentState.create classmethod accepts the arbitrary *children and **props arguments, and by default passes them to your get_component classmethod. These arguments may be used to customize the component, either by applying defaults or passing props to certain subcomponents.

In the following example, we implement an editable text component that allows the user to click on the text to turn it into an input field. If the user does not provide their own value or on_change props, then the defaults defined in the EditableText class will be used.

Click to edit

Edit me!

Reflex is fun

class EditableText(rx.ComponentState):
    text: str = "Click to edit"
    original_text: str
    editing: bool = False

    def start_editing(self, original_text: str):
        self.original_text = original_text
        self.editing = True

    def stop_editing(self):
        self.editing = False
        self.original_text = ""

    @classmethod
    def get_component(cls, **props):
        # Pop component-specific props with defaults before passing **props
        value = props.pop("value", cls.text)
        on_change = props.pop("on_change", cls.set_text)
        cursor = props.pop("cursor", "pointer")

        # Set the initial value of the State var.
        initial_value = props.pop("initial_value", None)
        if initial_value is not None:
            # Update the pydantic model to use the initial value as default.
            cls.__fields__["text"].default = initial_value

        # Form elements for editing, saving and reverting the text.
        edit_controls = rx.hstack(
            rx.input.root(
                rx.input(
                    value=value,
                    on_change=on_change,
                    **props,
                ),
                **props,
            ),
            rx.icon_button(
                rx.icon("x"),
                on_click=[
                    on_change(cls.original_text),
                    cls.stop_editing,
                ],
                type="button",
                color_scheme="red",
            ),
            rx.icon_button(rx.icon("check")),
            align="center",
            width="100%",
        )

        # Return the text or the form based on the editing Var.
        return rx.cond(
            cls.editing,
            rx.form(
                edit_controls,
                on_submit=lambda _: cls.stop_editing(),
            ),
            rx.text(
                value,
                on_click=cls.start_editing(value),
                cursor=cursor,
                **props,
            ),
        )


editable_text = EditableText.create


def editable_text_example():
    return rx.vstack(
        editable_text(),
        editable_text(
            initial_value="Edit me!", color="blue"
        ),
        editable_text(
            initial_value="Reflex is fun",
            font_family="monospace",
            width="100%",
        ),
    )

Because this EditableText component is designed to be reusable, it can handle the case where the value and on_change are linked to a normal global state.

Global state text

GLOBAL STATE TEXT

class EditableTextDemoState(rx.State):
    value: str = "Global state text"


def editable_text_with_global_state():
    return rx.vstack(
        editable_text(
            value=EditableTextDemoState.value,
            on_change=EditableTextDemoState.set_value,
        ),
        rx.text(EditableTextDemoState.value.upper()),
    )

The underlying state class of a ComponentState is accessible via the .State attribute. To use it, assign an instance of the component to a local variable, then include that instance in the page.

Total: 0

0

0

def counter_sum():
    counter1 = reusable_counter()
    counter2 = reusable_counter()
    return rx.vstack(
        rx.text(
            f"Total: {counter1.State.count + counter2.State.count}"
        ),
        counter1,
        counter2,
    )

Other components can also affect a ComponentState by referencing its event handlers or vars via the .State attribute.

0

def extended_counter():
    counter1 = reusable_counter()
    return rx.vstack(
        counter1,
        rx.hstack(
            rx.icon_button(
                rx.icon("step_back"),
                on_click=counter1.State.set_count(0),
            ),
            rx.icon_button(
                rx.icon("plus"),
                on_click=counter1.State.increment,
            ),
            rx.button(
                "Double",
                on_click=counter1.State.set_count(
                    counter1.State.count * 2
                ),
            ),
            rx.button(
                "Triple",
                on_click=counter1.State.set_count(
                    counter1.State.count * 3
                ),
            ),
        ),
    )
← OverviewOverview →

Did you find this useful?

HomeGalleryChangelogIntroductionHosting