On the expectations of refreshable + user storage #3233
-
DescriptionI came across a behaviour and I believe it's not immediately clear how it should work. It took me a while to narrow this down to a small example. Here it is: @ui.refreshable
def show_clicks():
ui.label(f"Clicks: {app.storage.user.get('clicks', 0)}")
@ui.page("/")
def main():
def update_storage():
app.storage.user["clicks"] = app.storage.user.get("clicks", 0) + 1
show_clicks.refresh()
ui.button("Click me", on_click=update_storage)
show_clicks()
ui.run(reload=True, storage_secret="some secret key") My expectation is that when I open multiple different sessions (safari, chrome, incognito etc) each individual user should get their own number of clicks, which partially happens when each user presses their own button. But in reality, when I press the button in one of the windows, that change is propagated to all users. Then, when I click on another button in another browser it increments it's own counter correctly but propagates is to the other users. I also checked the .nicegui folder for the per-user storage and the values inside are correct for each user, so the issue seems to be about the refreshable triggering for all users. Forcing reload of the web page returns the clicks to their expected/current value, discarding the value propagated from the other window. My first instinct was to try putting the refreshable function inside Triggering refresh using My interpretation is that since the refreshable function is defined outside a page function it will impact all users since it's a "shared server state". It's not clear to me this is a bug or not, or what internal mechanisms in refreshable are making it act like this, but not fully clear from the docs how it should work. I'm new to nicegui so this might also be a lack of interacting with the framework. |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 5 replies
-
Ok... after giving this a bit more thought maybe the behaviour can be summarised as:
So, what is the best way to declare these refreshable functions? all inside the page function? My project has many of these at the moment 😄 any suggestions? |
Beta Was this translation helpful? Give feedback.
-
I think you ran into a pretty common problem with Here is a solution inspired by discussion #2535: class ClickUI:
@ui.refreshable
def show(self):
ui.label(f"Clicks: {app.storage.user.get('clicks', 0)}")
@ui.page("/")
def main():
click_ui = ClickUI()
def update_storage():
app.storage.user["clicks"] = app.storage.user.get("clicks", 0) + 1
click_ui.show.refresh()
ui.button("Click me", on_click=update_storage)
click_ui.show() By moving the function definition into a class, its state is tied to the instance An alternative is to wrap the function explicitly rather than using def show():
ui.label(f"Clicks: {app.storage.user.get('clicks', 0)}")
@ui.page("/")
def main():
refreshable_show = ui.refreshable(show)
def update_storage():
app.storage.user["clicks"] = app.storage.user.get("clicks", 0) + 1
refreshable_show.refresh()
ui.button("Click me", on_click=update_storage)
refreshable_show() I tend to like this solution better than introducing a meaningless class. What do you think? |
Beta Was this translation helpful? Give feedback.
Hi @ruisilvestre-snapp,
I think you ran into a pretty common problem with
ui.refreshable
. You want to keep the refreshable implementation separate from the page using it. But by moving the decorated function into global scope, it shares state with other instances using this function.Here is a solution inspired by discussion #2535: