dash icon indicating copy to clipboard operation
dash copied to clipboard

[BUG] `dcc.Location` doesn't refresh current page - callback does nothing when returns same value as its current state

Open antonymilne opened this issue 1 year ago • 4 comments

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 href that 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 😅

antonymilne avatar Jun 27 '24 09:06 antonymilne

You can also refresh the page with a clientside_callback:

function() {
    window.location.reload()
}

T4rk1n avatar Jun 27 '24 15:06 T4rk1n

~~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.

jowlo avatar Jun 27 '24 15:06 jowlo

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.reload and other clientside callbacks that just update search (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 (hash doesn't seem to work here) - needs some fiddling around with query string though
  • use routing_callback_inputs={"refresh_": Input("interval", "n_intervals")} - only works with layout a 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).

antonymilne avatar Jun 27 '24 19:06 antonymilne

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.

AnnMarieW avatar Jun 28 '24 04:06 AnnMarieW