Closed stevej2608 closed 2 years ago
Hi @stevej2608
Typically, the callback is not placed inside the layout function.
Try adding this to the apps in your pages
folder in a file called register.py
register.py
import dash
from dash import Dash, dcc, html, Input, Output, State, callback
dash.register_page(__name__)
# This page is invoked by:
#
# http://localhost:8050/register?email=harrysmall%40gmail.com
def layout(email=None):
store_email = dcc.Store(id="email", data=email)
heading = html.H2(f"Register {email}?")
btn = html.Button("Click to register", id="btn")
confirm = html.H2(id="confirm")
return html.Div([heading, btn, confirm, store_email])
@callback(
Output("confirm", "children"),
Input("btn", "n_clicks"),
State("email", "data"),
prevent_initial_call=True,
)
def register_cb(clicks, email):
if clicks:
return f"User {email} has been registered"
else:
return None
Hi @AnnMarieW,
Thanks for the update. If you add the dcc.Store my nested callback works as well.
def layout(email=None):
store_email = dcc.Store(id="email", data=email)
heading = html.H2(f"Register {email}?")
btn = html.Button("Click to register", id="btn")
confirm = html.H2(id="confirm")
@callback(Output("confirm", "children"), Input("btn", "n_clicks"), State("email", "data"), prevent_initial_call=True)
def register_cb(clicks, email):
if clicks:
return f"User {email} has been regisered"
else:
return None
return html.Div([heading, btn, confirm, store_email])
I've done a bit more poking around the Dash code. The problem I've experienced is as a result of the callback and the context being baked in on the first invocation of layout(). The following invocations result in the callback & new context being saved in the GLOBAL_CALLBACK_MAP. The problem is this map is read once by Dash when the server starts and is ignored thereafter. The new context is never invoked.
If this could be addressed the dcc.Store work around would not be needed.
Cheers.
Hi @stevej2608
You could avoid the dcc.Store
by giving the heading
an id
and parsing the children
prop in the callback to get the email.
I confirmed with the Plotly team that putting callbacks inside the layout function like in your example is a Dash anti-pattern that is not supported. All callbacks must be defined before the server starts.
With multi-process servers and/or multiple users looking at the app: they all need to have the same understanding of what callbacks exist and what the global state is, irrespective of what callbacks have already been executed.
The following snippet uses a @callback nested in within the layout() function for the page. The layout function expects an email argument that's been decoded by Dash/Pages from the query-string. The layout function is called on start-up with email=None and again when the page is rendered with email='harrysmall@gmail.com'
The problem is when the button callback is triggered the email value is None, which is the context of the first call of layout() when it should be the second.
The button callback is registered and then re-registered on the second invocation of layout() in the Dash GLOBAL_CALLBACK_MAP. The second registration should also capture the new context and this context should be active when the button callback triggers.
I've spent several hours trying to bottom out what's going on. Any ideas?