plotly / plotly/dash

Returning dash.no_update in overlapping callback calls overwrites most recently set value

Open
#1,861 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug P3
Dominant language
Python
Stars
24.4k
Forks
2.3k
Avg merge
2d 7h
Merged PRs (30d)
13

Description

Describe your context
Please provide us your environment, so we can easily reproduce the issue.

dash                      2.0.0
dash-bootstrap-components 1.0.0
dash-core-components      2.0.0
dash-daq                  0.5.0
dash-google-oauth         1.2
dash-html-components      2.0.0
dash-renderer             1.4.1
dash-table                5.0.0
dash-uploader             0.6.0
jupyter-dash              0.4.0
  • if frontend related, tell us your Browser, Version and OS

    • MacOS Monterrey 12.0.1
    • Safari 15.1, Chrome 96.0.4664.93

Describe the bug

This bug is easier to reproduce when the app is running on a slow network, to simulate it I've added a time.sleep(2) call to the Python code.

A callback is called first, and then the same callback is called a second time before the first call has completed. The first call returns first, returning a value (eg. "1") to populate a Div's children with. The second call returns second, but this time returns dash.no_update (or raises PreventUpdate) to indicate to not change the Div's children.

Because dash.no_update was returned from the second call, the Div's child should be "1" (being set from the first call). Instead, the Div's child stays blank. This is because the second call (returning dash.no_update) restores whatever value the child was when the second call began (rather than leaving it unchanged).

To reproduce, run the app below. Click the button "Test 1" first, then immediately press "Test 2" after (within 2 seconds, before the call to time.sleep(2) finishes from "Test 1").

import dash
from dash import html
from dash.dependencies import Input, Output
import time

app = dash.Dash(__name__)

@app.callback(
	Output("contents", "children"),
	Input("test1", "n_clicks"),
	Input("test2", "n_clicks"),
	prevent_initial_call=True
)
def test(n_clicks1, n_clicks2):
	# Get the Id that triggered the callback, and the corresponding # of clicks
	trig_id = dash.callback_context.triggered[0]["prop_id"].split(".")[0]
	trig_clicks = dash.callback_context.triggered[0]["value"]
	
	print(f"Entering {trig_id}: {trig_clicks}")

	time.sleep(2)

	if trig_id == "test2":
		print(f"Exiting {trig_id}: {trig_clicks} => dash.no_update")
		return dash.no_update

	print(f"Exiting {trig_id}: {trig_clicks} => {n_clicks1}")
	return str(n_clicks1)

app.layout = html.Div(children=[
	html.Button(id="test1", children="Test 1"),
	html.Button(id="test2", children="Test 2"),
	html.Div(id="contents")
])

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

Expected behavior

Expected behavior was described above. Returning dash.no_update should not change the contents of the Div from the most recent set. Instead it restores the value that the Div was when the callback was first initiated (rather than restoring the value when the callback ended)

The console output from clicking "Test 1" then "Test 2" quickly afterwards is shown below (where => indicates the return value):

Entering test1: 1
Entering test2: 1
Exiting test1: 1 => 1
Exiting test2: 1 => dash.no_update

The Div should have the value indicated by the line "Exiting test1" (ie. "1"), instead it is blank.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

No project file or test is named. Start by running the provided Python reproduction, then trace how overlapping callback responses and dash.no_update are handled; done means the completed value from the earlier callback remains visible and a regression test covers the ordering.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend-api-design
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.