3

I'm using the Dash Upload component, which in turn uses react-dropzone.

I can drag a file into the component and the corresponding callback will fire.

I can then drag a different file into the component and the callback will fire again.

But, if I drag a file into the component (which fires the callback) and then drag the same file into the component again, the callback does not fire.

There's a demo app in this Gist that demonstrates the behavior.

Searching for similar problems (stack-overflow, github) suggests that this behavior is to be expected because from the browser's point of view nothing has changed. Both of those discussions seem to end up with solutions that involve setting the .value part of some element to '', so that the browser sees the second drop event as a change.

Chriddyp contributed links to the relevant bit of code in Dash and pointed me to the react-dropzone component.

Is there a way to make dropping the file twice in a row work in Dash using react-dropzone?

Thanks!

g.

hartzell
  • 93
  • 6

3 Answers3

2

In Dash, callbacks are invoked every time a property changes. If you upload the same file a second time, the properties (e.g. the file name) are unchanged, and the callback will thus not be invoked. This is expected behavior.

To ensure that a callback is invoked every time, you must ensure that the Input property actually changes. One option would be to add a new property to the Upload component similar to the n_clicks property of buttons, say n_uploads, which is incremented each time a file is uploaded.

The easiest solution for the problem at hand would probably be to use the custom dash uploader instead. Among other things, it supports uploading the same file multiple times.

emher
  • 5,634
  • 1
  • 21
  • 32
  • This solution does not work? This isn't modifying the contents/modified date/name of the file which is what Dash needs to be changed in order for the "state" of the file to be changed and for the callback to be triggered. – Sadaf Chowdhury Nov 01 '21 at 20:34
2

A bit late answer. I found a solution which is just reset the contents and filename to None in the Output of the callback.

A simple example

import dash
from dash import dcc, html
from dash.dependencies import Input, Output, State


app = dash.Dash(__name__)
app.layout = html.Div([
    dcc.Upload(html.Button('Upload File', id='btn_id'), id='upload_id'),
    html.P(id='show_id'),
])


@app.callback(
    Output('show_id', 'children'),
    Output('upload_id', 'contents'),
    Output('upload_id', 'filename'),
    Input('upload_id', 'contents'),
    State('upload_id', 'filename'),
    State('btn_id', 'n_clicks'),
)
def uploaded_a_file(contents, filename, n_clicks):
    if not contents:
        raise dash.exceptions.PreventUpdate

    msg = f'Uploaded {filename} for {n_clicks} time.'

    return msg, None, None


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

aura
  • 383
  • 7
  • 24
0

Another workaround, similar to @aura's, is to replace the upload component entirely with a callback. This strategy can be useful when replacing "contents" would lead to circular callbacks.

See https://github.com/plotly/dash-core-components/issues/816#issuecomment-1032635061

Jon T
  • 108
  • 6