Skip to content

App state persists across toggled components when using ft.use_state #6153

@EH-MLS

Description

@EH-MLS

Title

App state persists across toggled components when using ft.use_state (Python 3.12, Flet 0.80.5, Windows 11)

Environment

  • OS: Windows 11
  • Python: 3.12
  • Flet: 0.80.5
  • Repo: flet-dev/flet

Description

When toggling between two component trees that each hold their own @ft.observable app instance, the UI shows state from the first app even after switching to the second. The displayed class name and the rendered users don’t match the expected app instance after toggling.

Steps to Reproduce

  1. Run the sample below with Flet 0.80.5 / Python 3.12 on Windows 11.
  2. Click “Toggle App” to switch between App and DifferentApp.
  3. Observe the text showing the class instance and the rendered user list.

Expected Behavior

  • When DifferentApp is active, the text should show Class DifferentApp instance DifferentApp and the user list should be Alice2 / Bob2.
  • When App is active, the text should show Class App instance App with John/Jane/Foo.

Actual Behavior

  • After toggling, the UI shows Class DifferentApp instance App while rendering users from the first app (John/Jane/Foo), indicating state from the first app is reused across toggles.
  • Switching back keeps showing the first app state.

Code Sample

from dataclasses import dataclass, field
import flet as ft

@ft.observable
@dataclass
class User:
    first_name: str
    last_name: str
    def update(self, first_name: str, last_name: str):
        self.first_name = first_name
        self.last_name = last_name

@ft.observable
@dataclass
class App:
    users: list[User] = field(default_factory=list)
    def add_user(self, first_name: str, last_name: str):
        if first_name.strip() or last_name.strip():
            self.users.append(User(first_name, last_name))
    def delete_user(self, user: User):
        self.users.remove(user)

@ft.observable
@dataclass
class DifferentApp:
    users: list[User] = field(default_factory=list)
    def add_user(self, first_name: str, last_name: str):
        if first_name.strip() or last_name.strip():
            self.users.append(User(first_name, last_name))
    def delete_user(self, user: User):
        self.users.remove(user)

@ft.component
def UserView(user: User, app: App) -> ft.Control:
    delete_user = app.delete_user
    is_editing, set_is_editing = ft.use_state(False)
    new_first_name, set_new_first_name = ft.use_state(user.first_name)
    new_last_name, set_new_last_name = ft.use_state(user.last_name)
    def start_edit():
        set_new_first_name(user.first_name)
        set_new_last_name(user.last_name)
        set_is_editing(True)
    def save():
        user.update(new_first_name, new_last_name)
        set_is_editing(False)
    def cancel():
        set_is_editing(False)
    if not is_editing:
        return ft.Row([
            ft.Text(f"{user.first_name} {user.last_name}"),
            ft.Button("Edit", on_click=start_edit),
            ft.Button("Delete", on_click=lambda: delete_user(user)),
        ])
    return ft.Row([
        ft.TextField(label="First Name", value=new_first_name,
                    on_change=lambda e: set_new_first_name(e.control.value), width=180),
        ft.TextField(label="Last Name", value=new_last_name,
                    on_change=lambda e: set_new_last_name(e.control.value), width=180),
        ft.Button("Save", on_click=save),
        ft.Button("Cancel", on_click=cancel),
    ])

@ft.component
def AddUserForm(app) -> ft.Control:
    add_user = app.add_user
    new_first_name, set_new_first_name = ft.use_state("")
    new_last_name, set_new_last_name = ft.use_state("")
    def add_user_and_clear():
        add_user(new_first_name, new_last_name)
        set_new_first_name("")
        set_new_last_name("")
    return ft.Row(controls=[
        ft.TextField(label="First Name", width=200, value=new_first_name,
                     on_change=lambda e: set_new_first_name(e.control.value)),
        ft.TextField(label="Last Name", width=200, value=new_last_name,
                     on_change=lambda e: set_new_last_name(e.control.value)),
        ft.Button("Add", on_click=add_user_and_clear),
    ])

@ft.component
def AppView() -> list[ft.Control]:
    first_app, first_app_set = ft.use_state(True)
    def toggle_app(e: ft.Event[ft.Button]):
        new_first_app = not first_app
        first_app_set(new_first_app)
    bottom_bar = ft.Button("Toggle App", on_click=toggle_app)
    if first_app:
        app, _ = ft.use_state(App(users=[User("John", "Doe"),
                                         User("Jane", "Doe"),
                                         User("Foo", "Bar")]))
        text_control = ft.Text(f"Class {App.__name__} instance {app.__class__.__name__}")
        return [bottom_bar, text_control, AddUserForm(app),
                *[UserView(user, app) for user in app.users]]
    else:
        app2, _ = ft.use_state(DifferentApp(users=[User("Alice2", "Smith"),
                                                   User("Bob2", "Johnson")]))
        text_control = ft.Text(f"Class {DifferentApp.__name__} instance {app2.__class__.__name__}")
        return [bottom_bar, text_control, AddUserForm(app2),
                *[UserView(user, app2) for user in app2.users]]

ft.run(lambda page: page.render(AppView))

Screenshots

  • ![image1](
Image

)

  • ![image2](
Image

)

Additional Notes

It appears ft.use_state is preserving and reusing the first component’s observable instance across toggles. Guidance on resetting state or isolating component instances when toggling would be helpful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions