Announcing Dash Bio 1.0.0 🎉 : a one-stop-shop for bioinformatics and drug development visualizations.

Bug introduced in Dash v1.13.0 from dash v1.12.0? dcc.Location refreshes continuously even with refresh=False

Hello,

Something seems to have changed or broken between Dash v1.12 and Dash v1.13, with regards to dcc.Location and dynamic layouts

In Dash 1.12, the following works:

@app.callback(Output(‘page-content’, ‘children’),
[Input(‘url’, ‘pathname’)])
def display_page(pathname):
if pathname == ‘/apps/cumulative_pnl’:
return cumulative_pnl.return_layout()

cumulative_pnl.py looks like:

from dash.dependencies import Input, Output, State
import dash_html_components as html
import dash_core_components as dcc
import datetime

from . import render_graphs

from app import app



def return_layout():
    return html.Div(
        [
            datetime.datetime.now(),
            dcc.Location(id='url', refresh=False),
        ],
    )

each time I go to /apps/cumulative_pnl, the return_layout function is invoked and my layout renders once, showing the datetime as of page load

In Dash 1.13, it seems to get stuck in an infinite loop, triggering over and over, with the datetime continually updating.

I’ll note more details of this bug - it seems to occur when dcc.Location is defined inside of the function returning the layout and its id property is set to ‘url’ - if the id string is anything besides url, the infinite looping does not occur:

def return_layout():
    return html.Div(
        [
            datetime.datetime.now(),
            #dcc.Location(id='url', refresh=False), # broken
            dcc.Location(id='url_blah', refresh=False),  # works
        ],
    )

Thank you for reporting this, @giftculture. Can you please share a minimal working example so we can test it out locally as well. And is there a specific reason you’re using dcc.Location inside a function?

app.py:

import dash
import dash_bootstrap_components as dbc

app = dash.Dash(__name__,
                external_stylesheets=[dbc.themes.BOOTSTRAP],
                meta_tags=[
                    {"name": "viewport", "content": "width=device-width, initial-scale=1"}
                ])
server = app.server
app.config.suppress_callback_exceptions = True

index.py:

#!/home/mhale/.virtualenvs/dash_release_1.13/bin/python
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
import argparse

parser = argparse.ArgumentParser(description='simple example of reload behavior')
parser.add_argument('--debug', action='store_true', help='turn on flask/dash debug mode')

args = parser.parse_args()
debug = args.debug

from app import app
from apps import simple_example


app.layout = html.Div([
    html.Div(
        [
            dcc.Location(id='url', refresh=False),
        ],
        id="toc"
    ),
    html.Div(id='page-content'),
])

@app.callback(Output('page-content', 'children'),
              [Input('url', 'pathname')])
def display_page(pathname):
    if pathname == '/apps/simple_example':
        return simple_example.return_layout()

if __name__ == '__main__':
    if debug:
        app.run_server(host='0.0.0.0', port=8999, debug=True)
    else:
        app.run_server(host='0.0.0.0', port=8999, debug=False)

apps → simple_example.py:

from dash.dependencies import Input, Output, State
import dash_html_components as html
import dash_core_components as dcc
import datetime

from app import app



def return_layout():
    return html.Div(
        [
            datetime.datetime.now(),
            #dcc.Location(id='url', refresh=False), # broken
            dcc.Location(id='url_blah', refresh=False),  # works
        ],
    )

Thank you for reporting this, @giftculture. Can you please share a minimal working example so we can test it out locally as well. And is there a specific reason you’re using dcc.Location inside a function?

We’re using a function to return the layout rather than using a static so that we can do dynamic things inside of the layout before returning it to the app. dcc.Location is inside of that layout so that we can refer to parts of the URL in callbacks

Hi @giftculture ,
In the layout of the index.py file you already have dcc.Location(id='url', refresh=False) , but you’re saying that you need to declare the same dcc.Location(id='url', refresh=False) inside the simple_example.py layout? It can’t have a different id name?
I understand that causes the infinite loop, so maybe there is another way to refer parts of the URL in the callback…

You can probably use the dcc.Store inside the index.py file to store the url path and then a callback in the simple_example.py file to retrieve the data.

Hi @giftculture ,
In the layout of the index.py file you already have dcc.Location(id='url', refresh=False) , but you’re saying that you need to declare the same dcc.Location(id='url', refresh=False) inside the simple_example.py layout? It can’t have a different id name?
I understand that causes the infinite loop, so maybe there is another way to refer parts of the URL in the callback…
You can probably use the dcc.Store inside the index.py file to store the url path and then a callback in the simple_example.py file to retrieve the data.

FWIW, having a different id than “url” in the layout returned from the function certainly stops the infinite loop from happening, and it’s no problem to do that, I’m just curious why it causes the infinite loop to begin with if I have refresh=False in both places?

As far as having dcc.Location defined in index.py, I was doing it this way so that the main page (reachable without specifying /apps/simple_example in the URL would render, and I was under the impression that going to /apps/simple_example would execute the render_layout(), and thus replace the previous layout.

Is the root of the problem that I have the same id for dcc.Location in both index.py and in the simple_example app rather than using distinct id attributes?

Yes, that appears to be the root of the problem. I’ll look at it again, and if I see anything else that might be causing the problem I’ll let you know.