reflex-dev / reflex

🕸️ Web apps in pure Python 🐍
https://reflex.dev
Apache License 2.0
20.44k stars 1.18k forks source link

Support setting values at ComponentState creation time in `get_component` #3771

Open TimChild opened 3 months ago

TimChild commented 3 months ago

I'd like to be able to create re-usable components, but providing some initial default values when I create them.

For example:

def AppFunc():
    import reflex as rx

    class Counter(rx.ComponentState):
        # Define vars that change.
        count: int = 0

        # Define event handlers.
        def increment(self):
            self.count += 1

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

        @classmethod
        def get_component(cls, start_value: int = 1, **props):
            cls.count = start_value
            return rx.hstack(
                rx.button("Decrement", on_click=cls.decrement,
                          id="button-decrement"),
                rx.text(cls.count, id="counter-value"),
                rx.button("Increment", on_click=cls.increment,
                          id="button-increment"),
                **props,
            )

    app = rx.App(state=rx.State)

    @rx.page()
    def index() -> rx.Component:
        return rx.container(
            Counter.create(start_value = 5),
        )    

This would ideally create a counter that has a starting value of 5, but otherwise would work like a normal counter.

At the moment, while the counter does show 5, it also stops working.

I'm pretty sure this is because cls.count is actually an rx.Var, and it is being replaced with just an integer. So then the layout is being created with a fixed integer rather than the Var instance.

I think the solution to this will also relate to #3711 << Which is also something I would like to be able to do.

As a sort of workaround, you can instead do:

        ...
        def handle_load(self):
            self.count = self._initial_value

        @classmethod
        def get_component(cls, start_value: int = 1, **props):
            cls._initial_value = start_value

        ...
        counter = Counter.create(start_value=5)

        @rx.page(on_load=counter.State.handle_load)
        def index() ...

Which then does have the desired behaviour.

But, that's a lot of work to achieve something relatively simple.

Is there a different way of looking at this?

benedikt-bartscher commented 3 months ago

It's not optimal, but you could try to adjust the default value like this:

cls.get_fields()["count"].default = start_value
TimChild commented 3 months ago

Thanks! That's a much better temporary solution than mine!