Issue with `dcc.Location` used in Refresh Callbacks in Multi-Page Dash App

Hello everyone,

I’m currently developing a multi-page data app to insert and explore data from my database.

I’m facing an issue where my AG Grid components (and other elements displaying database data) are not refreshed when I display a page. To address this, I created a callback that detects the ID of dcc.Location on page display using the callback_context.triggered_id parameter. However, it doesn’t work as expected.

Here’s what happens:

  • When I land on a page, the dcc.Location ID is not triggered.
  • When I leave the page, the dcc.Location of the new page triggers the callback of the page I just left.

I’m using independent dcc.Location components on each page with unique IDs. I also tried using a global dcc.Location in app.py, but the behavior persists.

Could someone explain why this happens or provide guidance on how to ensure that each page refreshes its data properly upon display?

Here’s a callback example:

dcc.Location(id="url", refresh=False),

@callback(
    Output("grid", "rowData"),
    Output("last-refresh", "children", allow_duplicate=True),
    Input('url', 'pathname'),
    prevent_initial_call=True,
)
def refresh_data(pathname):
    ctx = callback_context
    triggered_id = ctx.triggered_id
    
    if  triggered_id="url":
        rowdata, message = refresh_grid()
        return rowdata, message
    else:
        return no_update, no_update

Thanks in advance for your help!

Hi @Fatima24 and welcome to the Dash community :slight_smile:

You should only use one dcc.Location component and if it’s in the main app.py then it will be accessible to any page.

There is not quite enough code here to understand the problem. Can you make a complete minimal example that will demonstrate the issue?

Hello @AnnMarieW, thank you for your reply !

When I tried using a unique dcc.Location across the app, the same behavior persisted. the url is only called in the callback of the page I left.

Here’s a minimal example of the code in which I commented out the specific dcc.Locations and used a unique URL here:

app.py

from dash import Dash, html, dcc, dash
import dash_bootstrap_components as dbc

app = Dash(
    __name__,
    use_pages=True, 
    suppress_callback_exceptions=True,
    external_stylesheets=[dbc.themes.BOOTSTRAP]
)

app.layout = html.Div([
    dcc.Location(id="url", refresh=False),
    dash.page_container,  
])

if __name__ == "__main__":
    app.run_server(debug=True)

page1.py

from dash import Dash ,html, dcc, Input, Output, callback_context, register_page, callback
from dash import dash
import dash_bootstrap_components as dbc

from datetime import datetime

register_page(__name__, path="/", name="Page 1")

layout = html.Div([
    html.H1("Page 1"),
    dcc.Location(id="url-page1"),
    dbc.NavbarSimple(
    [
        dbc.Button("page1", href="/", color="secondary", className="me-1"),
        dbc.Button("page2", href="/page2", color="secondary"),
    ],
    color="primary",
    dark=True,
    className="mb-2",
),
    html.Div(id="data-output-page1"),
    html.Button("Refresh Data", id="refresh-button-page1"),

])

@callback(
    Output("data-output-page1", "children"),
    # Input("url-page1", "pathname"),
    Input("url", "pathname"),
    Input("refresh-button-page1", "n_clicks"),
    prevent_initial_call=True,
)
def refresh_data(pathname, n_clicks):
    ctx = callback_context
    triggered_id = ctx.triggered_id
    print("callback of page 1, triggered_id : ",triggered_id)
    if triggered_id == "url":
        print("entered the if with url 1 triggered")
        return f"Page 1 data loaded at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
    elif triggered_id == "refresh-button-page1":
        return f"Data manually refreshed at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
    return "No update"

page2.py

from dash import html, dcc, Input, Output, callback_context, register_page, callback
from dash import dash
import dash_bootstrap_components as dbc

from datetime import datetime

register_page(__name__, path="/page2", name="Page 2")

layout = html.Div([
    html.H1("Page 2"),
    dcc.Location(id="url-page2"),
    dbc.NavbarSimple(
    [
        dbc.Button("page1", href="/", color="secondary", className="me-1"),
        dbc.Button("page2", href="/page2", color="secondary"),
    ],
    color="primary",
    dark=True,
    className="mb-2",
),
    html.Div(id="data-output-page2"),
    html.Button("Refresh Data", id="refresh-button-page2"),
])

@callback(
    Output("data-output-page2", "children"),
    # Input("url-page2", "pathname"),
    Input("url", "pathname"),
    Input("refresh-button-page2", "n_clicks"),
    prevent_initial_call=True,
)
def refresh_data(pathname, n_clicks):
    ctx = callback_context
    triggered_id = ctx.triggered_id
    print("callback of page 2, triggered_id : ",triggered_id)

    if triggered_id == "url":
        print("entered the if with url 2")
        return f"Page 2 data loaded at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
    elif triggered_id == "refresh-button-page2":
        return f"Data manually refreshed at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
    return "No update"
  • Terminal Output when I click on page2:
    callback of page 1, triggered_id : url
    entered the if with url 1 triggered

The callbacks are not triggered on page landing because of prevent_initial_call=True (I need it in my actual code because I have duplicate callback outputs across the page)

  • when I comment out the prevent_initial_call=True I get this as output in the Terminal when I click on page 1:
    callback of page 2, triggered_id : URL
    entered the if with url 2
    callback of page 1, triggered_id : None
    “None” shows that the id of dcc.Location is not triggered and I’m unsure why.

Are there other approaches to updating data when the page loads, or a way to keep the app continuously retrieving new data?

I hope this code illustrates the issue.
Thank you for your time!

Have you tried setting refresh=“callback-nav” of the Location component?

You might also have a Store component which keeps track of what should be displayed in the table. When you navigate to a new page, set the Store to some parameter and then have your callback trigger on the Store data.

@mworth

Looks like you are making this much more complicated than it needs to be. You actually don’t need to use any dcc.Location components. You can remove them all from your app.

Then try changing the callback on page 1 to


@callback(
    Output("data-output-page1", "children"),
    Input("refresh-button-page1", "n_clicks"),
)
def refresh_data(n_clicks):
    ctx = callback_context
    triggered_id = ctx.triggered_id
    print("callback of page 1, triggered_id : ",triggered_id)
    if triggered_id == "refresh-button-page1":
        return f"Data manually refreshed at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
    return "No update"

Same thing for page 2:


@callback(
    Output("data-output-page2", "children"),
    Input("refresh-button-page2", "n_clicks"),
)
def refresh_data(n_clicks):
    ctx = callback_context
    triggered_id = ctx.triggered_id
    print("callback of page 2, triggered_id : ",triggered_id)
    if triggered_id == "refresh-button-page2":
        return f"Data manually refreshed at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
    return "No update"

This will make the callback on each page trigger when the page loads.

To understand what’s going on, think of the dcc.Location as a global variable. There is only one URL, so it doesn’t help to have a dcc.Location on each page. When you click on a link, it updates the URL. There is a callback in Pages that loads the new layout when the URL changes. If you use the URL as a trigger, the components for the new page aren’t rendered yet, so it won’t trigger the callback on that page.

Hello @mworth, thank you for your comment.
Yes, I have tried the “callback-nav” option, but it didn’t work.
I have also considered using stores; however, I wanted to explore further to see if there might be a simpler solution.

Hello again @AnnMarieW, this callback makes the refresh button the only way to manually refresh data.
The reason I’m using a callback to refresh data on page load is that my grids do not update automatically. For instance, if I add a row or value to my grid (that inserts it into my db), I have to use the refresh button to see the changes. Additionally, when I navigate away from the page and return, the changes I made do not appear automatically unless I stop and re-execute the code. If it’s deployed it will always display the grid as it was at the moment of deployment.

Could you help me understand the reason for this behavior? Is there a way to ensure the pages stay updated without requiring the user to refresh manually?

Thank you!

In the callback I showed that the callback is fired when the page loads, even if you don’t click the button. In that callback , instead of return "No update" you could add logic to update the data.

1 Like

It works! Could this be because prevent_initial_call is not set to True? Does Dash trigger all callbacks by default?

If so, my issue is that I have multiple duplicate outputs and need prevent_initial_call to be set to True. How can I handle this? Should the callbacks — even if they have different logic/purposes — be merged?

You might be able to do just one simple callback like the one above that only updates the data on page load. But hard to know without knowing more about your project.

I have at least 2 or 3 more callbacks that add rows to the grid, insert data in the new rows, update existing cells, etc., but I could find a way to merge those into one callback.

Thank you for your help and timely responses!

1 Like