-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Synchronizing localStorage between tabs using browser events #2533
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow this is an awesome idea and good execution!
I did find a minor issue though
import reflex as rx
class State(rx.State):
sync1: str = rx.LocalStorage(sync=True)
sync2: str = rx.LocalStorage(sync=True)
def set_sync2(self, foo: str):
self.sync2 = foo
def index() -> rx.Component:
return rx.fragment(
rx.color_mode_button(rx.color_mode_icon(), float="right"),
rx.vstack(
rx.input(value=State.sync1, on_change=State.set_sync1),
rx.input(value=State.sync2, on_change=State.set_sync2),
),
)
# Create app instance and add index page.
app = rx.App()
app.add_page(index)
In this case, the user has overwritted set_sync2
and it doesn't accept the value
arg anymore and thus, does not work (throws an error in the console TypeError: State.set_sync2() got an unexpected keyword argument 'value'
).
I think the way to handle this is to implement an internal event handler that allows the setting of arbitrary values.
Have a look at state.py
on_load_internal
function. And also have a look at how client side storage is handled in hydrate_middleware.py
.
Consider replacing the client storage functionality of hydrate
(which we're trying to deprecate anyway) with an explicit hydrate_client_storage_internal
event handler defined on rx.State
. This event handler could take an arbitrary dict and apply the values to the state.
Let me know if you have any further questions about this suggestions, and thanks again for the awesome contribution.
How did you come up that fast with this edge case? 🥇 You are right, I have tried your example and I understand the issue with it. Of course I asked myself why the caller of a setter needs to know the names of arguments of a setter, but maybe that is some python/reflex-detail that I'm not aware of. I could not help notice though that the compiler doesn't seem to have any problems with the const on_change_[...] = useCallback((_e0) => addEvents([Event("state.state.set_sync2", {foo:_e0.target.value})], (_e0), {}), [addEvents, Event]) My idea would be to use some of that generation logic to have nice events with proper payloads in Javascript. So my suggestion would be: Let's throw an error with a better message: "Please name your setter argument 'value'." |
Thanks again for your review @masenf and many thanks for your work on this awesome framework. When re-reading my last post I realized that I was not posing any concrete questions about your suggestions. I'm sorry about that.
So I want to lay out a set of implementation options. Maybe you have a better opinion that me as a reflex-noob.
(Regarding my background: Last August I started using python again after a fifteen year break. So my favorite solutions are the combination of 1. and 2. or even only 2. :) |
Apply fully qualified var names to each substate they are associated with. This allows consistent updates to arbitrary state vars without having to know their "setter" arguments, in case the user has overwritted the `set_x` name.
@abulvenz Apologies for the delay; we're preparing for a huge release for the last week and I didn't have time to get back to you earlier. I have pushed to your branch with my suggested change and added a test using that I've added a new Have a look at the changes and let me know if they make sense. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @masenf .
I like your changes and especially the test!
Feel free to merge it like that, I tested it locally and it seems to work as expected.
One minor point that might be an architectural decision:
The overridden setters are not called due to the use of setattr
instead of going via the EventHandler
s. If that is the intended behavior, maybe we should mention that in the documentation.
Thanks for your effort!
This is indeed the intended behavior. Any overridden setter will be triggered by the tab that initiates the change to the var. That setter will update the state and the final value will be pushed into local storage from that tab. Once the |
Thanks for the explanation @masenf. I just reassured myself that |
Thank you, @masenf ! |
This PR creates a synchronization mechanism between different tabs. When state-relevant localStorage variables are changed in different tabs it will cause a state change event in the current tab.
The default is off, so the behavior will not change.
When you want to use the sync effect, do it like this:
I have tested it manually with the client storage example from the homepage.
I hope that
context.js.jinja
template, but I didn't see how I could add it there.driver.switch_to.new_window("tab")
Note this PR was inspired by the ease and lightning fast reaction when changing the dark/light mode using the
rx.color_mode_button()
. Maybe in the future, such features can also rely on the state.All Submissions:
Type of change
Please delete options that are not relevant.
Tiny one.
New Feature Submission:
Changes To Core Features:
Have you added an explanation of what your changes do and why you'd like us to include them?
Have you written new tests for your core changes, as applicable?
Not yet
Did you successfully run tests with your changes locally?