flexx icon indicating copy to clipboard operation
flexx copied to clipboard

Use case for a post_init method

Open Konubinix opened this issue 4 years ago • 2 comments

Hi,

Trying to use the central data store idea from the sensible usage patterns, there is a situation I don't manage de handle elegantly.

My application is contains a State and a Frontend that react to state changes.

I want the frontend to be initialized according to the state initial values, but it appears that it never get any event about the state initialization.

Say that the State indicates a number of children that the frontend should have. I want to have something like the following.

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

from flexx import flx, config
config.tornado_debug = True
config.hostname = "0.0.0.0"


class State(flx.JsComponent):
    number_of_children = flx.IntProp(5, settable=True)


class Frontend(flx.VBox):
    def init(self):
        self.placeholders = []

    @flx.reaction("root.state.number_of_children")
    def setup_placeholders(self):
        for placeholder in self.placeholders:
            placeholder.dispose()
        with self:
            self.placeholders = [
                flx.Label(text=str(i))
                for i in range(self.root.state.number_of_children)
            ]


class TestApp(flx.PyComponent):
    state = flx.ComponentProp()
    frontend = flx.ComponentProp()

    def init(self):
        self._mutate_frontend(Frontend())
        self._mutate_state(State())


def main():
    flx.App(TestApp).serve()
    flx.start()


if __name__ == "__main__":
    main()

With this example, the frontend is initialized empty. Using the inspector, I can change the value of the state and see the frontend reacting correctly.

I thought that maybe the initial event (the one that has old_value == new_value) was missed somehow. So I tried delaying the setting of number_of_children by changing State with:

class State(flx.JsComponent):
    number_of_children = flx.IntProp(0, settable=True)

    def init(self):
        self.set_number_of_children(5)

Even then, no event caught.

I also tried calling the setup_placeholders method inside Frontend.init, but I get "[E 09:15:41 flexx.app] JS: Uncaught TypeError: Cannot read property 'number_of_children' of null - stack trace in browser console (hit F12).".

Then I fall back on:

class State(flx.JsComponent):
    number_of_children = flx.IntProp(0, settable=True)

    def init(self):
        window.setTimeout(self.post_init, 0)

    def post_init(self):
        self.set_number_of_children(5)

Now, the frontend is correctly initialized. This looks to me like an poor way of initializing my state. I think that may be there is a good practice on the order of structuring or declaring the widgets to make sure the widgets correctly use the initial value of the state?

Konubinix avatar Jul 10 '20 07:07 Konubinix

When State gets instantiated, its properties get initialized, and an initial event is emitted for each property, but that initial event can only be captured by the object itself, because any external reactions will connect after the object has been initialized. Implementing State.init() would not help, because that method is also called as part of the instantiating. Once instantiated, it becomes the value of the state property of the TestApp, and then the reaction in the frontend gets connected.

So, that's why it does not work. Now how to deal with this ... I think the most elegant way is to call setup_placeholders in thet fronend's init().

almarklein avatar Jul 27 '20 10:07 almarklein

So, that's why it does not work. Now how to deal with this ... I think the most elegant way is to call setup_placeholders in thet fronend's init().

This is exactly what I eventually did :-D. I was thinking about it and did not come with a better solution.

Konubinix avatar Jul 27 '20 13:07 Konubinix