I have a dash application that uses the EventListener from dash-extensions (@Emil)
I have a websocket server that sends payloads/data to the frontend. some of these payloads are notifications (from dash mantine components @snehilvj)
I have noticed that below 100ms gap between events being ommited, dash drops callbacks and my application does not emit the correct notifications
Here is a basic example. I commented out the notifications from dash mantine to show that it isnt the problem but dash seems to be. when you run this you see on the console that the id’s get printed in the callback but the frontend does not display these
requirements.txt
blinker==1.6.2
cachelib==0.9.0
click==8.1.3
dash==2.9.3
dash-core-components==2.0.0
dash-extensions==1.0.0
dash-html-components==2.0.0
dash-mantine-components==0.12.1
dash-table==5.0.0
EditorConfig==0.12.3
Flask==2.3.2
Flask-Caching==2.0.2
itsdangerous==2.1.2
Jinja2==3.1.2
jsbeautifier==1.14.7
MarkupSafe==2.1.2
more-itertools==9.1.0
packaging==23.1
plotly==5.14.1
six==1.16.0
tenacity==8.2.2
Werkzeug==2.3.4
app.py
# create a basic dash app
import dash
from dash import html, ClientsideFunction, Patch
import dash_mantine_components as dmc
from dash_extensions.enrich import DashProxy, html, Input, Output, State,MultiplexerTransform
from dash_extensions import EventListener
from flask import cli
app = DashProxy(__name__, transforms=[MultiplexerTransform()])
event = {"event": "my-event", "props": ["detail"]}
# app.layout = dmc.NotificationsProvider(
# html.Div(
# [
# EventListener(
# events=[event],
# id="custom-event-listener"
# ),
# html.Div([], id="notifications-container"),
# dmc.Button("Show Notification", id="notify"),
# dmc.NumberInput(
# label="Delay between events",
# description="From 0 to infinity, in steps of 5",
# value=5,
# min=5,
# max=1000,
# step=5,
# style={"width": 250},
# id="message-delay"
# )
# ]
# ),
# limit=20
# )
app.layout =html.Div([
EventListener(
events=[event],
id="custom-event-listener"
),
dmc.Button("Emit Events", id="notify"),
dmc.Button("Clear", id="clear-btn", style={"marginLeft": "10px"}),
dmc.NumberInput(
label="Delay between events",
description="From 0 to infinity, in steps of 5",
value=100,
min=5,
max=1000,
step=10,
style={"width": 250},
id="message-delay"
),
html.Div([], id="notifications-container"),
]
)
app.enable_dev_tools(debug=True)
app.config.suppress_callback_exceptions = True
@app.callback(
Output("notifications-container", "children"),
Input("clear-btn", "n_clicks"),
prevent_initial_call=True,
)
def clear(clicks):
if not clicks:
raise dash.exceptions.PreventUpdate
return []
@app.callback(
Output("notifications-container", "children"),
Input("custom-event-listener", "n_events"),
State("custom-event-listener", "event"),
prevent_initial_call=True,
)
def notify1(n_events, event):
if not n_events:
raise dash.exceptions.PreventUpdate
notification = event["detail"]
print(n_events)
p = Patch()
p.append(html.Div(notification["id"]))
return p
app.clientside_callback(
ClientsideFunction(
namespace='demo',
function_name='send_events'
),
Output("notify", "data-dummy"),
Input("notify", "n_clicks"),
State("message-delay", "value"),
prevent_initial_call=True,
)
if __name__ == "__main__":
app.run_server(debug=True)
script.js
const sendMessage = message => {
document.dispatchEvent(new CustomEvent('my-event', { detail: message }))
}
window.dash_clientside = Object.assign({}, window.dash_clientside, {
demo: {
send_events: (clicks, timeout) => {
const ITER = 3
for (let i = 0; i < ITER; i++) {
const id = (clicks -1) * ITER + i;
const event = {
id: `notification[${i}]-${id}`,
title: `Notification[${i}] Title ${id}`,
message: `Notification[${i}] Message ${id}`,
autoClose: 2000,
color: `red`,
action: `show`
}
setTimeout(() =>sendMessage(event), i * timeout)
}
throw dash_clientside.PreventUpdate;
}
}
})