on page unload event
In react there is a beforeunload event which can be triggered when user closes browser window, how to do this in reflex? Went through reflex/page.py...would it be added here?
I am actually trying to implement something similar today. Now, I'm going to advise against implementing something on the entire page, as it can cause performance issues (i.e. Firefox's bfcache won't cache pages if there is a beforeunload listener attached). It is also advised to use it sparingly, like when the user actually has unsaved changes like in an un-submitted form.
I created a MutationAwareState(rx.State) subclass, so I only implement it on states and vars that matter.
from typing import Any
import reflex as rx
class MutationAwareState(rx.State):
"""State class aware of base var mutations."""
__user_modified_vars__: list[str] = []
__before_unload_enabled__: bool = False
@rx.event
async def track_mutations(self):
"""Enable or disable the `beforeunload` listener accordingly."""
if self.__user_modified_vars__ and not self.__before_unload_enabled__:
yield rx.call_script('window.addEventListener("beforeunload", beforeUnloadHandler);')
self.__before_unload_enabled__ = True
if not self.__user_modified_vars__ and self.__before_unload_enabled__:
yield rx.call_script('window.removeEventListener("beforeunload", beforeUnloadHandler);')
self.__before_unload_enabled__ = False
def __setattr__(self, name: str, value: Any):
"""Check our var and value, update tracking list accordingly, then set the var."""
if name in self.base_vars:
if name not in self.__user_modified_vars__ and name != rx.constants.ROUTER:
self.__user_modified_vars__.append(name)
if name in self.__user_modified_vars__ and self.get_fields()[name].default == value:
self.__user_modified_vars__.remove(name)
super().__setattr__(name, value)
You need to include the beforeUnloadHandler script, so add it to you app head components so it's available on every page:
app = rx.App(
head_components=[
rx.script(
"""
const beforeUnloadHandler = (event) => {
// Recommended
event.preventDefault();
// Included for legacy support, e.g. Chrome/Edge < 119
event.returnValue = true;
};"""
),
]
)
Now implement in your state that tracks your desired user input vars. I create a set_... for each var I want to track 1) for nicer IDE type hinting and static typing and 2) so I can add the track_mutations event (you can use other events, this is just what I use):
class MyFormState(MutationAwareState):
username: str = ""
email: str = ""
password: str = "" # There's no explicit setter, but this will still be tracked
@rx.event
async def set_username(self, value: str):
self.username = value
yield MyFormState.track_mutations
@rx.event
async def set_email(self, value: str):
self.email = value
yield MyFormState.track_mutations
There are a few caveats:
- It only tracks base vars, not computed or backed. It DOES however include inherited base vars, so be careful with subclassing.
- Even if you don't specifically implement a setter for a base var, its mutations are still tracked. However, only events that chain the
track_mutationsevent will enable/disable the script. - If there are vars you DON'T want to be tracked, create them as backend vars and use computed for the front end renders.
In an ideal scenario, much like how Pydantic uses their Field() class to set default, default_factory, etc, the Reflex team should configure their rx.field method to include some of those features that may be parsed by their states, like if it should trigger the beforeunload event if its value isn't a default. It's a tall ask though.
In react there is a
beforeunloadevent which can be triggered when user closes browser window, how to do this in reflex? Went throughreflex/page.py...would it be added here?
I think the only way right now would be to spwan a background event which every n seconds would check if the current connection (session id) is still active. Not perfect but probably effective.