5 challenges when using Plotly Dash for web apps

Nik Vaklev
Analytics Vidhya
Published in
5 min readMay 7, 2020

--

My first proper encounter with Dash was relatively recently. I partnered with a company to host a Dash app in Google Cloud. Working with Dash was OK but the overall experience was not great. These are a few of the hiccups I encountered.

Persisting information in the web page itself

Dash apps are meant to be stateless and this is for a good reason. Normally the app will be run as multiple Python processes behind one server in order to serve multiple requests simultaneously. For any simple app using a stateless server is the best starting point because sharing state in-memory between these processes is just asking for trouble. Dash resolves this issue by storing the data in hidden elements on the webpage itself, thus a simple reload wipes out any stored information about the user session. Dash on the client side uses a Redux store it seems but there is no access to it.

OK, not a big deal if all you do is displaying a few charts but this is a real problem for any interactive applications.

Creating interactivity is challenging

The main mechanism in Dash for creating interactivity is server callbacks. In short, the browser calls the server and any updates to the DOM are sent back to the client, which is less efficient than executing Javascript code inside the browser. Recently client-side callbacks have been added to Dash but I couldn’t work out how they are triggered except when (re)loading the webapge. I used a client callback to run a Javascript code when the page loads. It definitely saved the day but the approach has limitations. The callback has to be defined inside the Python code on the server though its execution has nothing to do with the server:

# Python client-side callback definition
app.clientside_callback(
ClientsideFunction(
namespace='clientside',
function_name='DoSomething'
),
# the output field must match a property that is already defined
# Output('div-id', 'style'),
Output('div-id', 'style'),
[Input('MyButton', 'none')]
)

The code above declares that a function called DoSomething will be “triggered” by MyButton and update thestyle property of a div with id div-id .

This is the corresponding Javascript code on the client side:

# Javascript code
(function(){
if(!window.dash_clientside) {window.dash_clientside = {};}
window.dash_clientside.clientside = {

DoSomething: () => {
// ADD CODE HERE
return {display: 'none'}
},
}
})();

This is what the element looks like:

# Python code defining the page elements
html.Div(className='style-me-with-css', id='DIV-ID', style={'display': 'none'}, children=[
// DO SOMETHING HERE
])

For instance, if the Divdefined above does not have explicitly the style property referenced in the Python callback, the Javascript code will not be executed in the browser. Confused?

Conclusion: this is a very convoluted scheme trying to achieve something that should be rather simple.

It is difficult to customize the API

In my project, the Dash app was hosted behind an API gateway, which takes care of user authentication. Here I hit a wall trying to get Dash to work with the gateway. The reason was rather unfortunate. The root for any dash app must have forward slash at the end: https://my.dashserver.com/app1/

This URL htts://my.dashserver.com/app1 won’t work.

I thought that was a strange choice but hey ho. Then I realised that my gateway couldn’t work URLs with a slash at the end!

Solution: hack the underlying Flask app to add a home-page route without the forward slash at the end of it. Arrgghhh!

Yes, it did work out just fine eventually but it was not easy! The major drawback for me is the lack of built-in methods to extend the Dash API/ functionality. You have to hack the Flask app.

No lists in the Dash HTML

Crazy! Yes, there is no simple way of defining a list. This is what I did:

# Python server code
html.Div([
# li 1
html.P([
html.B('1. '),
html.Span('Click here')
]),
# li 2
html.P([
html.B('2. '),
html.Span('Click here')
]),
...
])

Again, it does the job especially when combined with a bit of CSS, but it does not look great!

That brings me to the last point: it is not possible to write plain HTML with Dash. An easy workaround for the case above would have been a Python component which can render any HTML. Then I could easily render text, lists, etc. Look at the code above! Writing a simple paragraph with a bit of text formatting requires dedication!

Update [11/05/2020]: It turns out it is possible to have lists and raw HTML using the Markdown component:

dcc.Markdown('''
* list item A
* list item B
* ...
<ol>
<li>item one</li>
<li>item two</li>
<li>...</li>
</ol>
''')

dcc.Markdown accepts a boolean flag called dangerously_allow_html making it possible to write raw HTML after all.

I discovered all of this by randomly browsing the latest Dash documentation.

Oh, one last thing

That’s my favourite! It is impossible for two Python callbacks to update the same element! I know! This is so much fun. You are not allowed to do this:

@app.callback([Output('target-div', 'children')],
[Input('upload-data', 'contents')])
def callback1(contents, filename):
return PreventUpdate
@app.callback([Output('target-div', 'children')],
[Input('upload-data-two', 'contents')])
def callback1(contents, filename2):
return PreventUpdate

I found out that resolving this issue has been a feature request since 2018: https://community.plotly.com/t/why-can-an-output-only-have-a-single-callback-function/14691

Hence, developers might be tempted to create one almighty callback which accepts all the possible inputs and outputs and based on some convoluted logic sends back the “right” response.

A colleague on the project did come up with an elegant solution. Since there are no limitations on the inputs, it is OK to add extra inputs to some callbacks as to trigger different responses based on the combinations of inputs. The above example then becomes this:

@app.callback([Output('target-div', 'children')],
[Input('upload-data', 'contents'),
Input('upload-data-two', 'contents')])
def callback1(contents, filename):
ctx = dash.callback_context
source = ctx.triggered[0]['prop_id'].split('.')[0]
if source == 'upload-data':
return 'happy days'
elif source == 'upload-data-two':
return 'this is another way of updating the same element'

Conclusion

Dash does work OK when you want to create a simple dashboard and host it on your laptop. For any other situation you either need to pay them to host it for you and take advantage of their enterprise features or hack your way through.

Also creating interactive applications, which requires a complex UI, is not recommended. In my next project I want to try Bokeh. Their documentation makes more sense just looking at it!

My name is Nik Vaklev and I am the founder of Techccino Ltd | Data Visualisation and Serverless Apps. If you are interested in building your own web apps get in touch.

--

--