A minimum working example follows below.
The idea behind the app is that the user can add data to the table by either clicking at the individual checkboxes or by using a shortcut and clicking at a button in which case both checkboxes will be checked/unchecked and they should update the table accordingly.
What I would expect to happen:
- User clicks at the button, changing its ‘n_clicks’ property.
- The button’s click triggers a callback.
- This callback updates both checkboxes ‘checked’ property.
- As both checkboxes were update, therefore two chained callbacks would be triggered.
- Two new rows of data would be added to the table, one for each chained callback.
What is actually happening:
- User clicks at the button, changing its ‘n_clicks’ property.
- The button’s click triggers a callback.
- This callback updates both checkboxes ‘checked’ property.
- One chained callback is triggered.
- One new row of data is added to the table.
What is the Dash Callback concept I’m missing to understand why only one chained callback is being triggered and what’s the workaround?
Thank you.
# Imports.
import dash
import polars as pl
import dash_mantine_components as dmc
from dash.exceptions import PreventUpdate
from dash import Dash, Input, State, Output, ALL, dash_table
def setup_layout(app):
# App's layout.
app.layout=dmc.MantineProvider(
dmc.Group(
align='start',
children=[
dmc.Stack(
dmc.Button(id='button'),
),
dmc.Stack([
dmc.Checkbox(id={'id_name': 'checkbox', 'id_index': 'A'}),
dmc.Checkbox(id={'id_name': 'checkbox', 'id_index': 'B'})
]),
dmc.Stack(
dash_table.DataTable(
id='dash_table',
data=df.to_dicts(),
columns=[{"name": i, "id": i} for i in df.columns],
)
)
])
)
def setup_button_callback(app):
# Callback triggered by the button's click to update all the checkboxes accordingly.
@app.callback(
output=dict(
out_checkboxes=Output({'id_name': 'checkbox', 'id_index': ALL}, 'checked')
),
inputs=dict(
click=Input('button', 'n_clicks'),
in_checkboxes=State({'id_name': 'checkbox', 'id_index': ALL}, 'checked')
),
prevent_initial_call=True
)
def function(click, in_checkboxes):
# Prevent erros.
if not click:
raise PreventUpdate
# Return the inverse of the checkbos state. This means that If the
# checkbox is checked, return False. If the checkbox is not checked,
# return true.
return dict(
out_checkboxes=[
not in_checkboxes[0], # Output for 1st checkbox.
not in_checkboxes[1] # Output for 2nd checkbox.
]
)
def setup_checkbox_callback(app):
# Callback triggered by the checkboxes's click to update the dash table.
@app.callback(
output=dict(
new_table_data=Output('dash_table', 'data')
),
inputs=dict(
in_checkboxes=Input({'id_name': 'checkbox', 'id_index': ALL}, 'checked'),
actual_table_data=State('dash_table', 'data')
),
prevent_initial_call=True
)
def function(in_checkboxes, actual_table_data):
# Prevent erros.
if in_checkboxes == None:
raise PreventUpdate
# Adds new data to the dash table.
actual_table_data.append(dict(Name='Dummy Data'))
# Return the new data to the dash table.
return dict(
new_table_data=actual_table_data
)
# Runs from this script.
if __name__=='__main__':
# Sets the correct react version to use latest DMC version.
dash._dash_renderer._set_react_version('18.2.0')
# Creates the Dash's app.
app = Dash(__name__)
# Dummy Data for the Dash Table.
df = pl.DataFrame({'Name': ['John Doe', 'Jane Doe']})
# Setup the layout.
setup_layout(app)
# Setup the callbacks.
setup_button_callback(app)
setup_checkbox_callback(app)
# Runs the app locally.
app.run_server(debug=True, port=8050)