This is a continuation of my previous post, where I’m using this code to make the responsive navbar feature
I’m getting this error when uploading the layout with a grid (id’s: daggrid
or daggrid2
)
A nonexistent object was used in an
Input
of a Dash callback.
This problem started happening after making this navbar, because now the dropdowns are always active, but they are triggering a callback whose input is not yet loaded, just like explained here, but with the active tab instead.
However, I can’t simply remove the dropdowns as input like in that example, because they are necessary to update the graph’s content, I don’t want to make they States and use a button to trigger the callback either, because I don’t the user to be moving the cursor every time and the n_submit
prop which triggers a callback when the user presses ‘Enter’ is only available for dcc.Input
afaik.
Therefore, the only way to make it work I can think is to make dash wait a few ms before triggering the callback when I change the page, then it works normally.
Here is the code for the MRE:
Summary
app.py
import dash
from dash import Dash, html, dcc, callback, Input, Output, State
import dash_ag_grid as dag
import dash_mantine_components as dmc
import plotly.express as px
import pandas as pd
app = Dash(__name__, use_pages=True, suppress_callback_exceptions=True)
server = app.server
df = px.data.tips()
df = df.to_dict('records')
iterator = 0
page = list(dash.page_registry.values())
menu1 = dmc.Menu(
[
dmc.MenuTarget(dmc.Button('Pages')),
dmc.MenuDropdown(
[
dmc.MenuItem(
'Page 1',
href=page[0]['path'],
),
dmc.MenuItem(
'Page 2',
href=page[1]['path'],
),
]
)
]
)
navwidth = 1
#----------------------------------------------------------------------------------------------------
app.layout = \
html.Div(
children=[
dmc.Navbar(
id='sidebar',
fixed=False,
hidden=True,
width={"base": navwidth},
position='right',
children=[],
style={
"overflow": "hidden",
"transition": "width 0.3s ease-in-out",
"background-color": "#f4f6f9",
},
),
html.Div(
children=[
dmc.Grid(
children=[
dmc.Col(
dmc.Burger(id='sidebar-button'),
span='content',
),
dmc.Col(
html.Div(
children=[
"Minimal reproducible example"
],
style={'fontSize': 30, 'textAlign': 'left'}),
span='content', offset=2),
dmc.Col(menu1, span='content', offset=0),
]),
html.Hr(),
html.Div(
children=[
dash.page_container,
],
)
],
style={"display": "flex", "maxWidth": "100vw", "overflow": "hidden",
"flexGrow": "1", "maxHeight": "100%", "flexDirection": "column"},
id="content-container"
),
#Store and Location components
dcc.Location(id='url', refresh=True),
dcc.Store(id='data-store', data=df),
dcc.Store(id='page-changes-store', data=iterator),
],
style={"display": "flex", "maxWidth": "100vw", "overflow": "hidden", "maxHeight": "100vh",
"position": "absolute", "top": 0, "left": 0, "width": "100vw"},
id="overall-container"
)
#--------------------------------------------------------------------
@callback(
Output("sidebar", "width"),
Input("sidebar-button", "opened"),
State("sidebar", "width"),
prevent_initial_call=True,
)
def drawer_demo(opened, width):
if width["base"] == navwidth:
return {"base": 200}
else:
return {"base": navwidth}
def pg1():
drop1 = dmc.Select(id='drop1', data=['sex', 'smoker'], value='sex')
return drop1
def pg2():
drop2 = dmc.Select(id='drop2', data=['sex', 'smoker'], value='sex')
return drop2
@callback(
Output('sidebar', 'children'),
Output('page-changes-store', 'data'),
Input('url', 'pathname'),
State('page-changes-store', 'data'),
)
def nav_content(url, iterator):
print('\nNum of times the page was changed:', iterator)
iterator += 1
nav_content = {
'/': pg1(),
'/pg2': pg2(),
}.get(url)
return nav_content, iterator
if __name__ == "__main__":
app.run(debug=True, port=8060)
pg1.py
import dash
from dash import Dash, html, dcc, Input, Output, State, callback, ctx
import dash_ag_grid as dag
from dash.exceptions import PreventUpdate
import dash_mantine_components as dmc
import plotly.express as px
import pandas as pd
from datetime import datetime
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
dash.register_page(__name__, name='Page 1', path='/')
layout = \
html.Div([
html.Div(id='grid-div', children=[]),
html.Div(id='plot-div'),
])
#-------------------------------------------------------------------------------------------------
@callback(
Output('grid-div', 'children'),
Input('data-store', 'data'),
)
def make_grid(data):
df = pd.DataFrame(data)
grid = dag.AgGrid(
id='daggrid',
rowData=data,
columnDefs=[{'field': i} for i in df.columns]
)
return grid
@callback(
Output('plot-div', 'children'),
Input('daggrid', 'virtualRowData'),
Input('drop1', 'value'),
prevent_initial_call=True
)
def filter_data(rows, drop):
now = datetime.now()
current_time = now.strftime('%T.%f')[:-3]
comp_id = ctx.triggered_id if not None else 'nothing'
print('\nPage 1 callback was triggered by:', comp_id)
if not rows:
print('Page 1 rows is 0 | Time:', current_time)
raise PreventUpdate
print('Page 1 rows is not 0 | Time:', current_time)
print('Page 1 rows is None:', rows is None)
dff = pd.DataFrame(rows)
fig = px.scatter(dff, x='total_bill', y='tip', color=drop)
return dcc.Graph(figure=fig)
if __name__ == '__main__':
app.run(debug=True)
pg2.py
import dash
from dash import Dash, html, dcc, Input, Output, State, callback, ctx
import dash_ag_grid as dag
from dash.exceptions import PreventUpdate
import dash_mantine_components as dmc
import plotly.express as px
import pandas as pd
from datetime import datetime
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
dash.register_page(__name__, name='Page 2', path='/pg2')
layout = \
html.Div([
html.Div(id='grid-div2', children=[]),
html.Div(id='plot-div2'),
])
#-------------------------------------------------------------------------------------------------
@callback(
Output('grid-div2', 'children'),
Input('data-store', 'data'),
)
def make_grid(data):
df = pd.DataFrame(data)
grid = dag.AgGrid(
id='daggrid2',
rowData=data,
columnDefs=[{'field': i} for i in df.columns]
)
return grid
@callback(
Output('plot-div2', 'children'),
Input('daggrid2', 'virtualRowData'),
Input('drop2', 'value'),
)
def filter_data(rows, drop):
now = datetime.now()
current_time = now.strftime('%T.%f')[:-3]
comp_id = ctx.triggered_id if not None else 'nothing'
print('\nPage 1 callback was triggered by:', comp_id)
if not rows:
print('Page 2 rows is 0 | Time:', current_time)
raise PreventUpdate
print('Page 2 rows is not 0 | Time:', current_time)
print('Page 2 rows is None:', rows is None)
dff = pd.DataFrame(rows)
fig = px.scatter(dff, x='total_bill', y='tip', color=drop)
return dcc.Graph(figure=fig)
if __name__ == '__main__':
app.run(debug=True)
Important observations:
- I already used
suppress_callback_exceptions=True
as an option in the main app; - I’m also using
dcc.Store
to share data between pages, otherwise this problem doesn’t happen because the grid would load before the callback. But I want to reuse the grid instead of creating it on each page, it’s also useful for caching; - Page 1 uses
prevent_initial_call=True
while page 2 doesn’t, this makes the error message as soon the app starts if I start it on page 2 but not page 1; - Despite what I said, the
ctx.triggered_id
tells it’s the grids themselves that are triggering the callback, but it’s not them that are causing the problem, usingtime.sleep()
inside thenav_content(url, iterator)
function in the main app makes it clear that is the dropdown that triggers the callback; - When the page is refreshed, the grid
virtualRowData
is initially 0, then it’s loaded, but usingpreventUpdate
when it’s recognized as 0 is not enough to supress the error message, as per point 3.