Update multiple, with one callback

In my app, I have an option two create a new project, when a button is clicked its reads the state of the input value. Then a new project is created in the database. The user is also updated if the request was successful or not. The part I am struggling with is updating the dropdown, containing all the projects. Because a callback function can only output one component. I have to choose, either show the user creating the project was successful or updating the dropdown. I did try to create a second callback function to update the dropdown. This drop-down is than only updated on a page reload…

@app.callback(Output('project_add_status', 'children'), [Input('create_project_button', 'n_clicks')], [State('input_box_project_name', 'value')])
def create_project(n_clicks, value):
	if value:
		try:
			with database.atomic():
				# Attempt to create a new project, If the project name is already taken, due the
				# unique constraint, the database will raise an IntegrityError
				project = Project.create(name=value)
				for project in Project.select():
					print(project.name)
				return 'Project ' + value + ' is added'
			
		except IntegrityError:
			return 'Project name is already taken'

	else:
		pass

@app.callback(Output('project_drop_down', 'options'), [Input('project_drop_down', 'value')])
def update_drop_down_project(value):
	# Safely reassign the filter to a new variable
	print('test')
	values = {}
	for project in Project.select():
		values.update({'label': project.name, 'value': project.name})

	return [values]

Maybe the above does not make any sense, so this is what I want to do:

  • When the button is clicked, update the database with the new project.
  • Show the user if this was successful or not
  • Update the dropdown

Problem

This is indeed the eternal problem in Dash (not unfairly, this is hard stuff), getting callbacks to affect multiple things at once.

I do two things to get around this in my apps:

  • Create multiple callbacks based on the same input (the button click, in your, case I think), so one callback can update the database, and the other the dropdown

  • then utilize dozense of conditional statements within all these callbacks to ensure that the logic between them always makes sense, even if one callback get updated in a weird way before another one.

1 Like

He Mike thanks,

I found a workaround, I am now just returning the HTML code that contains the button, and if the creation of the project was successful. For anybody interested in the code:

def register_callbacks(app):

@app.callback(Output('project_card', 'children'), [Input('create_project_button', 'n_clicks')], [State('input_box_project_name', 'value')])
def create_project(n_clicks, value):
	response = None
	if value:
		try:
			with database.atomic():
				# Attempt to create a new project, If the project name is already taken, due the
				# unique constraint, the database will raise an IntegrityError
				project = Project.create(name=value)
				response = 'Project ' + value + ' is added'
			
		except IntegrityError:
			response = 'Project name is already taken'
			
	else:
		pass
	
	return project_card(response)

And the function for producing the card:

def project_card(value):

array = []
for project in Project.select():
	values = ({'label': project.name, 'value': project.name})
	array.append(values)
	
card = dbc.CardColumns(
	[
		dbc.Card(
			[
				dbc.CardHeader("Project"),
				dbc.CardBody(
					[
						dcc.Dropdown(
							id='project_drop_down',
							options=array,
							value=0
						),
						add_new_project(value),
						dcc.Upload(
							id='upload-data',
							children=html.Div([
								'Drag and Drop or ',
								html.A('Select Files')
							]),
							style={
								'width': '95%',
								'height': '60px',
								'lineHeight': '60px',
								'borderWidth': '1px',
								'borderStyle': 'dashed',
								'borderRadius': '5px',
								'textAlign': 'center',
								'margin': '10px'
							},
							# Allow multiple files to be uploaded
							multiple=True
						),
						html.Div(id='output-data-upload'),
					]
				),
			],
			color="white"
		),
		dbc.Card(
			[
				dbc.CardHeader('Files')
			]
		)
	],

)
return card

Btw I just had look at the roadmap

Seems that this in the process!

I’m really glad you found a workaround meanwhile, Matthijs.

Also, looking around the new updates in the forum, it seems callbacks with multiple outputs have just become supported here. What happy timing, if you would want to take advantage of that, :).