Upload file to update Dropdown component


#1

I’ve been trying to make a dashboard where I can populate the filters (dash core components like Dropdown, etc.) and figures after I upload a file using the Upload component.

For Sharing data between callbacks, I am using a hidden Div to store the CSV contents as json.

I’m facing a problem updating the dropdown options in a callback using the ‘children’ attribute of the hidden Div or the contents attribute of Upload component.

Here is the smallest complete snippet I created:

import base64
import datetime
import io

import pandas as pd

import dash

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

app = dash.Dash(__name__)

app.title = 'Trial App'

app.layout = html.Div(
    [
        dcc.Upload(
            id='file_upload',
            children=[
                'File Select'
            ]
        ),
        html.Div(
            id='uploaded_file',
            style={
                'display' : 'none'
            }
        ),
        dcc.Dropdown(
            id='ride_drop'
        )
    ]
)

def parse_contents(contents, filename, date):
    content_type, content_string = contents.split(',')

    decoded = base64.b64decode(content_string)

    df = pd.read_csv(io.StringIO(decoded.decode('utf-8')))

    return df



@app.callback(
    Output('uploaded_file', 'children'),
    [Input('file_upload', 'contents')],
    [State('file_upload', 'filename'),
    State('file_upload', 'last_modified')
    ]
)
def update_hidden_div(content, name, date):
    #
    if content is not None:
        df = parse_contents(content, name, date)
        jsonified_dataframe = df.to_json(
            date_format='iso',
            orient='split'
        )
        print('[INFO] Hidden Div updated')
        return [
            jsonified_dataframe
        ]

@app.callback(
    Output('ride_drop', 'options'),
    [Input('file_upload', 'contents')],
    [State('uploaded_file', 'children')]
)
def update_ride_dropdown(content, jsonified_dataframe):
    if content is not None:
        df = pd.read_json(jsonified_dataframe, orient='split')
        print('[INFO] Dataframe is of size: {}'.format(df.shape[0]))

        rides = df['ride_num'].unique()

        return [
            { 
                'label': r,
                'value' : r
            } for r in rides
        ]



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

Here is the output:

The server seems to not respond (execute the print statements). And the Dropdown is not populated. The data is correct, I checked.

Any help is appreciated. Thanks!

Edit:

If I remove the second callback to update the Dropdown component, the server seems to respond.


Can the value of a dcc.Dropdown be set via callback?
#2

The problem is that on page load all callbacks are fired once. If you look at your second callback, it will initially fire and the value you’re targeting will be None so it will not read from your uploaded file, but then your function will return implicitly None (the default return value in Python), which will set the value of your Dropdown’s options property to be None, when it needs to be a list. This then breaks in the Dash render. If you open up the console in the developer tools you’ll see:

Search.js:137 Uncaught (in promise) TypeError: Cannot read property 'length' of null

The solution is to return an empty dictionary if the data your testing against is None (or raise PreventUpdate)

Also, I think your second callback might want to be listening to changes on the contents of the hidden div (and not for changes in the uploaded file component – I don’t think it should be involved in that callback at all?). After all, that’s what you’re after. I think as it is you might have a race condition where it’s possible for the second callback to complete before the first has saved the div (relevant when you have more than one worker process).