I’m trying to build a dash app with an upload box (dcc.Upload()) and a datatable (dash_table.DataTable()) where the names of files uploaded through the upload box are supposed to be added to the table. Files seem to be successfully uploaded - the filenames of uploaded files are printed correctly to the console by the print statement inside the update_file_table() callback function. However, no new rows appear in the DataTable. Code is below. Also, on startup of the app, I see an error that says:
TypeError: Cannot read property ‘forEach’ of null
Code:
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import dash_table
dashapp.layout = html.Div([
dcc.Upload(
id = ‘file-upload’,
children = html.Div([
'Drag and drop here or ',
html.A(‘click here to select files’)
]),
style = {
‘width’: ‘100%’,
‘height’: ‘60px’,
‘lineHeight’: ‘60px’,
‘borderWidth’: ‘1px’,
‘borderStyle’: ‘dashed’,
‘borderRadius’: ‘5px’,
‘textAlign’: ‘center’,
‘margin’: ‘10px’
},
multiple = True
),
dash_table.DataTable(
id = 'file-table',
columns = [{'name': 'Filename', 'id': 'Filename'}],
row_selectable = 'multi',
row_deletable = True,
data = [],
),
])
@dashapp.callback(
Output(component_id=‘file-table’, component_property=‘data’),
[Input(component_id=‘file-upload’, component_property=‘contents’)],
[State(component_id=‘file-upload’, component_property=‘filename’),
State(component_id=‘file-upload’, component_property=‘last_modified’)]
)
def update_file_table(list_of_contents, list_of_filenames, list_of_last_modified):
if list_of_contents is not None:
[jvdata.append(JVData(f)) for f in list_of_filenames]
data = [{'Filename': jv.filepath} for jv in jvdata]
print(data)
return data
JVData is a class that contains (among other things) the filepath as an instance variable.
In your update_file_table function, you should also check list_of_filenames for is not None as you do not set filename to a default value on page load. Thus, on app startup, this parameter is None. I suspect this could be the problem.
I found a fix, although I’m not entirely clear on why it works -
Updating not just the ‘data’ field of the DataTable, but also the ‘columns’ field makes it correctly show uploaded files. Relevant code (where the callback is defined) -
@dashapp.callback(
[Output(component_id='file-table', component_property='data'),
Output(component_id='file-table', component_property='columns')],
[Input(component_id='file-upload', component_property='contents')],
[State(component_id='file-upload', component_property='filename')]
)
def update_file_table(list_of_contents, list_of_filenames):
if list_of_contents is not None and list_of_filenames is not None:
# For each uploaded file, add a JV object to the global list of JV curves
[jvdata.append(JVData(f)) for f in list_of_filenames]
# Create a list of dicts with filenames of uploaded files
data = [{'Filename': jv.filepath} for jv in jvdata]
#debug
print(data)
# Return this list to file-table DataTable
return data, [{'id': 'Filename', 'name': 'Filename'}]
return [], []
I am running into this same issue. The datatable is not rendered/populated upon upload. The callback function listens for content property. Here’s a semi-reproducible example/code:
Any help/pointers would be much appreciated.
# App Layout
app.layout = html.Div([
html.H3("Post Covid-19 Tool"),
dcc.Upload(
id='upload-data',
children=html.Div([
'Drag and Drop or ',
html.A('Select Files')
]),
style={
'width': '100%',
'height': '60px',
'lineHeight': '60px',
'borderWidth': '1px',
'borderStyle': 'dashed',
'borderRadius': '5px',
'textAlign': 'center',
'margin': '10px'
},
# Allow multiple files to be uploaded
multiple=False
),
html.Div([
dash_table.DataTable(
id='emp-table',
page_size = 14,
sort_action='native',
row_selectable='multi',
filter_action='native',
row_deletable=True,
editable=True
),
])
])
# In[12]:
# file upload function
def parse_contents(contents, filename):
content_type, content_string = contents.split(',')
decoded = base64.b64decode(content_string)
try:
if 'csv' in filename:
# Assume that the user uploaded a CSV file
df = pd.read_csv(io.StringIO(decoded.decode('utf-8')))
elif 'xls' in filename:
# Assume that the user uploaded an excel file
df = pd.read_excel(io.BytesIO(decoded))
except Exception as e:
print(e)
return None
return df
# Return Datatable
@app.callback([Output('emp-table', 'data'),
Output('emp-table', 'columns')],
[Input('upload-data', 'contents')],
[State('upload-data', 'filename')])
def render_table(contents, filename):
if contents and filename:
df_geo = parse_contents(contents, filename)
columns = [{"name": i, "id": i} for i in df_geo.columns]
data = df_geo.to_dict('records')
# Debug
print(df_geo.head())
return (data, columns)
else:
return []
if __name__ == '__main__':
app.run_server(debug=True, port=8000)
@keval This seems to be related to an issue in dash-table that seemingly breaks a DataTable when filter_action is enabled along with one or more of the following:
A workaround would be to disable filter_action (deleting the following line from your example makes it work fine for me) - filter_action='native',
Of course, this is not ideal because it means you cannot perform the filtering.
A more recent answer from @montovaneli on the Github issue page provides a fix -
Returning empty lists if there is no data is what causes the problem. Instead of return [], []
use return start_table_df.to_dict('records'), [{'id': '', 'name': ''}]
and it should work