Capture window/tab closing event

Hi,

Since Dash does not allow the use of global variables, I was saving the data in the user’s browser, but the process of deserialization is very slow. I have decided to create a session identifier and save the data in a dictionary that separates the data into sessions. However, when a session ends I can’t free space in the dictionary because I don’t know how to capture that event.

Can you help me?

Thank you,

FJR2

2 Likes

Hm, good point. This isn’t possible right now, but it’s a good idea. One way that this could be implemented is through a custom Dash component with a parameter that listens to the beforeUnload JS event and fires an update to Dash. In fact, it looks like there is already a React component written in the community that does this: react-beforeunload - npm. Here is the tutorial on creating custom Dash components in React: Build Your Own Components | Dash for Python Documentation | Plotly


Very nice solution! A couple of notes on this:

Note that this might not work in a multi-process app. If there are multiple people using the app, then you’ll want to run the app with multiple workers (e.g. with gunicorn with $ gunicorn app:server --workers 4 --threads 2) so that multiple requests can be handled in parallel. The dict that you create and mutate during the lifetime of the app won’t be shared across workers: one request could cache the result on one worker and the next request could end up using a different worker (without the cached value!). For more on this, see Global variables sharing with mutex - #4 by nedned

However, you could use a similar pattern but write the data to the disk (you’ll still have to serialize and unserialize it). Also, you could limit the cache store to be roughly the size of the number of concurrent users - so, if you expect 10 users, the 11th entry in your cache store would get dropped - and fall back on the original, uncached solution if it’s not in your session store.

Here is an example that writes the data to the disk and only holds N items in the session store:

import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
import datetime
from flask_caching import Cache
import pandas as pd
import time
import uuid

app = dash.Dash()
cache = Cache(app.server, config={
    'CACHE_TYPE': 'filesystem',
    'CACHE_DIR': 'cache-directory',
    'CACHE_THRESHOLD': 50  # should be equal to maximum number of active users
})

@cache.memoize()
def query_data(session_id):
    df = pd.DataFrame({'a': [datetime.datetime.now()]})
    return df.to_json()


def dataframe(session_id):
    return pd.read_json(query_data(session_id))

def serve_layout():
    session_id = str(uuid.uuid4())
    df = dataframe(session_id)
    return html.Div([
        html.Div(session_id, id='session-id', style={'display': 'none'}),
        dcc.Dropdown(id='dropdown', value='a', options=[
            {'label': i, 'value': i} for i in ['a', 'b', 'c', 'd']
        ]),
        html.Pre(id='output')
    ])

app.layout = serve_layout


@app.callback(Output('output', 'children'),
              [Input('dropdown', 'value'),
               Input('session-id', 'children')])
def display_value(value, session_id):
    df = dataframe(session_id)
    return df.to_csv()


if __name__ == '__main__':
    app.run_server(debug=True)
2 Likes

Thanks for your quick answer @chriddyp . I don’t know anything about JS, but I’ll try to look at it.

Hi @FJR2 and @chriddyp , I’m not sure if this is still needed but I just made my first custom component that handles this event. You can check it out at https://github.com/ONLYstcm/DASH-Unload-Component

2 Likes

Not entirely true if you use the preload option and multiprocessing compatible variables.

@thatguy Thank you so much for sharing! Your custom component works great!

Hm. I’m having an error when installing the python package as instructed. do you have some idea of what I might have wrong? I get these errors on my firefox console. It’s hard for me to catch the “syntax error” because the corresponding js file is minimized. thanks in advance.
image

Are you sure you installed the module properly? If you had a problem with installing the module then please post the installation-related error on github. We can move forward from there

1 Like

I’m not doing much in terms of installation, other than instructed.
On my Ubuntu 18.04 64bits:

 conda create --clone base -n dash-unload
 conda activate dash-unload
 pip install dash-unload-component
 pip install dash dash_html_components
(create dunload.py with suggested demo contents)
 python *.py
 

Then I get this on my Firefox console:
image

It seems I’m getting some argument type incompability.

thanks.

I’m aware of this error message

SyntaxError: Unexpected token ‘:’

It was consistent throughout the time I developed the component and still is. I’m not experienced enough to get rid of it, however it never affected the behaviour of the component from my experience, which is why I ignored it.

Are your dash components and react module updated to the latest version?

The react module might very well be the issue. I never installed any since I have not used it and assumed it was all packaged as part of the pip install dash-unload-component

The rest of the installation is fresh from my last post.

Any suggestions on what I should follow, re: react?

Thanks.

FYI: I just added the React Developer Tools to my Firefox, but didn’t get anything different.

Well if you have a working dash environment then I assume you should have react installed already. As dash components need React to render. As for what to do, I’m not too sure because I’m just a novice in javascript. But the error i32 vs f64 seems to relate to integer and float incompatibility I think? So I think updating your react (assuming it is outdated) might fix the issue.

Also React Dev Tools is just for more advanced debugging it won’t solve the problem but you can use it to try figure out the root of the problem

right. all seems to be at the latest version, tough. I will post if I find something. Thanks for the follow-up.

I’m not sure why serialization is needed for this example or for Example 4 given on https://dash.plotly.com/sharing-data-between-callbacks (which was based on this post). Flask-Cache uses the Werkzeug cache, which uses the pickling library to serialize ANY object.

For this example, you can change return df.to_json() to return df and return pd.read_json(query_data(session_id)) to return query_data(session_id). Similarly for Example 4. This would be a lot more simple.