dash: Dash Multi-Page App URLs do not always execute in the same order
Link/Location/pathname + callbacks issues
It appears that the callbacks driven by pathname from a Location
component do not always execute in the same order, leading to behavior like on the https://plot.ly/dash/urls page. Refreshing the urls page quickly and repeatedly leads to one of three cases:
- The home Plotly Dash page is displayed
- The home Plotly Dash page is flickered and then the URLs page is displayed
- The URLs page is displayed.
As a result, trying to have multi-page apps driven by the URL does not work, since callbacks either
- Receive the pathname as
None
(assuming that would map to case 1 above), or - Do not update at all and pathname keeps the old value (assuming that would also map to case 1 above), or
- It updates after other callbacks have fired, leading to more requests than necessary (assuming that would map to case 2 above), or
- Execute as expected, but less than all of the time.
Example
The Multi-Page App URLs demo app confirms this behavior, as the initial page load keeps a ‘404’ on the page regardless if I directly go to /apps/app1
in the URL bar in Chrome.
I updated the display_page
callback in the example to
@app.callback(Output('page-content', 'children'),
[Input('url', 'pathname')])
def display_page(pathname):
print(pathname)
if pathname == '/pages/app1':
return app1.layout
elif pathname == '/pages/app2':
return 'hi!'
else:
return '404'
This creates this stack trace:
* Running on http://127.0.0.1:8889/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 246-948-826
127.0.0.1 - - [13/Sep/2017 15:14:49] "GET /apps/app1 HTTP/1.1" 200 -
127.0.0.1 - - [13/Sep/2017 15:14:50] "GET /_dash-layout HTTP/1.1" 200 -
127.0.0.1 - - [13/Sep/2017 15:14:50] "GET /_dash-dependencies HTTP/1.1" 200 -
127.0.0.1 - - [13/Sep/2017 15:14:50] "GET /favicon.ico HTTP/1.1" 200 -
None
127.0.0.1 - - [13/Sep/2017 15:14:50] "POST /_dash-update-component HTTP/1.1" 200 -
/apps/app1
127.0.0.1 - - [13/Sep/2017 15:14:50] "POST /_dash-update-component HTTP/1.1" 200 -
The page just renders 404. When I include a dcc.Link('CHANGE URL', href='/pages/app1')
component on the page, and click on that, then it changes to the app1 content.
Am I doing something wrong? I copied the exact project structure for this demo, am also seeing it in other apps I’m working on, and there seems to be a clear issue with the https://plot.ly/dash/urls page.
About this issue
- Original URL
- State: closed
- Created 7 years ago
- Comments: 15 (13 by maintainers)
Commits related to this issue
- remove page flicker as per discussion https://github.com/plotly/dash/issues/133 — committed to plotly/dash-docs by chriddyp 7 years ago
- add back subpar page flicker there is a deeper bug here uh oh https://github.com/plotly/dash/issues/133#issuecomment-330717153 — committed to plotly/dash-docs by chriddyp 7 years ago
- add failing test this test demonstrates the issue found in https://github.com/plotly/dash/issues/133#issuecomment-332299141 — committed to plotly/dash-renderer by chriddyp 7 years ago
- reject old responses of the same output component fixes https://github.com/plotly/dash/issues/133 — committed to plotly/dash-renderer by chriddyp 7 years ago
- Update R package to 1.0.1 (#133) — committed to HammadTheOne/dash by rpkyle 5 years ago
- Update R package to 1.0.1 (#133) — committed to HammadTheOne/dash by rpkyle 5 years ago
- Develop (#133) * 3.1 props refactoring (#106) * refactor virtualization, virtualization settings, and derived props * refactor renaming paging / pagination props * refactor virtual, viewport and p... — committed to HammadTheOne/dash by Marc-Andre-Rivet 6 years ago
- Develop (#133) * 3.1 props refactoring (#106) * refactor virtualization, virtualization settings, and derived props * refactor renaming paging / pagination props * refactor virtual, viewport and p... — committed to plotly/dash by Marc-Andre-Rivet 6 years ago
- Update R package to 1.0.1 (#133) — committed to plotly/dash by rpkyle 5 years ago
Nevermind, I’m able to reproduce the issue now:
The issue occurs when the intial request with
pathname=None
takes longer than the follow up request withpathname='/page-1'
. While dash initiates the requests in order, it looks like we aren’t doing a good job of dealing with request responses that come back out of orderi just had this exact problem trying to build a similar url-based routing. this solution worked for me after trying every possible if-then, and try-except combination possible to trap the NoneType error. this bug appears to still be there. (active_route refers to a bus route, not an url route)
Fixed in
dash-renderer==0.11.0
🎉If this works the way it is supposed to, then there should only be 1 extra / unnecessary API call: the first callback with
pathname=None
. In the grand scheme of things, this shouldn’t be a issue given that the requests and responses JSON payloads are going to be extremely tiny (the request is something like{payload: {inputs: [{id: 'location', 'prop': 'pathname', 'value': null}], outputs: [{id: 'content', 'prop': 'children'}]}
and the response is just something like{'id': 'location', 'prop': 'pathname', 'value': ''}
) . However, it is certainly confusing and idiosyncratic. I don’t see a good way around it given the current architecture.For now, I’ll look into getting this to work the way it is supposed to 😅
@mjclawar Thanks for opening the issue! Here’s what’s going on here: 1 - Unspecified properties in the
app.layout
are set toNone
2 - When the app loads, it renders thelayout
and then it fires off all of the callbacks that have inputs and outputs that are visible in the app. In this case,dcc.Location.pathname
isNone
, so that gets fired off. 3 - When thedcc.Location
component is rendered, itspathname
property is updated to match the URL https://github.com/plotly/dash-core-components/blob/2c7c3c08319c10639eea3b8c00ae12d399eff4c7/src/components/Location.react.js#L13-L19. In doing this, it fires off a callback with it’s newpathname
property.We can’t actually set the
pathname
to a string in theapp.layout
because the app could be loaded from any url. If we set it to/page-2
, then the initial callback would be fired with/page-2
even if the user loaded/page-3
.The solution that was taken in the https://plot.ly/dash/urls page and in the dash-docs (https://github.com/plotly/dash-docs/blob/c2c74e718a492217502b03739e39190c359af029/tutorial/run.py#L286-L287) was to load the index page if the pathname was
None
and “hope” that the user indeed loaded that URL. As you mention, this causes the page to flicker if the URL was wrong (the index content will be loaded on the first callback whenpathname=None
and the correct page’s content will be loaded whendcc.Location
component is rendered).The correct solution would probably be to load either a loading message or an empty string on
None
:In fact, I’m going to update the dash-docs to do that right now.
By updating the content in-place with use of the
dcc.Link
component, Dash apps are able to provide a multi-page app experience without refreshing the entire page - it’s extremely fast! Going forward, usingdcc.Location
anddcc.Link
is the method that I “officially recommend”.