[BUG] `dcc.Location` doesn't refresh current page - callback does nothing when returns same value as its current state
Describe your context
There's a chance this isn't a bug and is instead just my incompetence but I'm pretty flummoxed by it so hopefully someone can explain either way!
dash 2.17.1
dash_ag_grid 31.2.0
dash-bootstrap-components 1.6.0
dash-core-components 2.0.0
dash-html-components 2.0.0
dash-mantine-components 0.12.1
dash-table 5.0.0
dash-testing-stub 0.0.2
Describe the bug
How can I get the current page to refresh periodically in Dash? I would expect the following to work:
import datetime
from dash import Dash, html, dcc, State, Input, Output, callback
def layout():
return html.Div(
[
dcc.Location(id="url", refresh=True),
dcc.Interval(id="interval", n_intervals=0),
html.H1(datetime.datetime.now()),
]
)
@callback(Output("url", "href"), Input("interval", "n_intervals"), State("url", "href"))
def f(n_intervals, href):
print(f"Running callback with {n_intervals=}\t{href=}")
return href
app = Dash(__name__)
app.layout = layout
app.run()
In fact, I would also expect refresh="callback-nav" to work for this but it doesn't either. Not sure what refresh=False should do in this case, but it also doesn't refresh the page.
What I observe is:
- manually refreshing page updates the time shown as expected
- altering the callback to do
return href + "a"does the refresh the page and keeps on appending"a"to the path as expected - hence there is something particular about not changing the value of
hrefthat means the callback runs but does not do anything
Is this a general Dash feature whereby properties whose state is unchanged in a callback does not do anything? (Note I am not explicitly doing dash.no_update in the above but just returning the same value that is the current state.) I've tried adding a dummy second output to the above callback whose value does change and that updates as expected but the page still does not refresh.
If this is an expected Dash feature then how can I achieve the page refresh? I feel like I'm missing something obvious here 😅
You can also refresh the page with a clientside_callback:
function() {
window.location.reload()
}
~~You are updating the href part of the location which is the part of an URL after the # and usually does not trigger a page refresh. What you would want to change is the pathname property of the Location.~~
edit: Nevermind, mixed up href and hash there, rest is still true.
But, it is actually a feature that a refresh is only triggered in case there is a change of that property (see Location.js)
As a way to get around this, you could update the search with a random value, which triggers a page reload.
A better way would be to just update the component (the time in this example) directly, without ever needing page reload at all.
Thanks very much for the replies @T4rk1n and @jowlo.
@T4rk1n: this does indeed work, although does not do exactly what I'm actually looking for in practice (will explain below).
@jowlo: updating search also works and the current behaviour makes sense now I see the source for Location.js, thank you for that 👍 Agree that in general it makes more sense to just update the specific components rather than doing a whole page reload, but this is a bit of a special case.
In reality, the above was a just MWE. What I'm actually interested in here is the case of Dash pages and automatically reloading the page in the same way that refresh="callback-nav" works (i.e. not a full page refresh). So the MWE actually looks like this:
import dash
import datetime
from dash import Dash, html, dcc, State, Input, Output, callback, clientside_callback, page_container
def layout(**kwargs):
return html.H1(datetime.datetime.now())
app = Dash(__name__, use_pages=True, pages_folder="")
dash.register_page("/", layout=layout)
app.layout = html.Div(
[
dcc.Location(id="url", refresh="callback-nav"),
dcc.Interval(id="interval", n_intervals=0),
page_container,
]
)
app.run()
These solutions do a full refresh of the page as if refresh=True:
- @T4rk1n solution with
window.reloadand other clientside callbacks that just updatesearch(e.g. https://stackoverflow.com/questions/5999118/how-can-i-add-or-update-a-query-string-parameter) - adding
meta_tags=[{"http-equiv": "refresh", "content": "2"}])(no callbacks needed at all)
But actually what I'd really like to do is just refresh the content of page_container (i.e. re-run the whole layout function). This is possible when you move between different pages (through either a dcc.Link or a callback with output dcc.Location(refresh="callback-nav"), they act the same way AFAIU). What I'd like to achieve is the same "light refresh" behaviour on the same page.
Judging by Location.js, it looks like some clientside callback involving window.dispatchEvent(new CustomEvent('_dashprivate_pushstate') might achieve it but I couldn't get it working and it doesn't look like a safe public method obviously.
These two solutions work in doing the "light refresh":
- @jowlo solution of updating
search(hashdoesn't seem to work here) - needs some fiddling around with query string though - use
routing_callback_inputs={"refresh_": Input("interval", "n_intervals")}- only works withlayouta function
But I sort of feel like this should be possible just by updating href of dcc.Location. Curious what you think @AnnMarieW? I guess it's equivalent to asking whether a dcc.Link that links to the page you are on should do a light refresh or nothing (current behaviour which does make sense but was just not what I was expecting).
Hi @antonymilne If I can get more information on your use case, I might be able to help with a workaround. Do you have an open issue over in the Vizro Github? Feel free PM me if you like.