-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Open
Labels
P3backlogbacklogbugsomething brokensomething brokendash-data-tablerelated to DataTable componentrelated to DataTable component
Description
Describe your context
dash_bootstrap_components==1.2.1
dash==2.7.0
Describe the bug
I have implemented a simple editor, which enables you to add and delete rows using a pattern-matching-callback. Each row contains a delete-button, which is target of a dbc.Tooltip
.
When deleting the very last row, an error occurs when returning adjusted children for div selection-area
:
An object was provided as `children` instead of a component, string, or number (or list of those). Check the children property that looks something like:
{
"props": {
"children": [
{
"props": {
"children": [
null,
{
"props": {
"is_open": false
}
}
]
}
}
]
}
}
- According to the console prints in my code below, the callback is unexpectedly called twice by the delete-button-pattern-input when clicking delete on the very last row.
- The second time the callback is called, the last element of the input
selection_elements
is the object from the above error.
- The second time the callback is called, the last element of the input
- If we set a breakpoint on the return command at the end of the callback or apply
time.sleep(1)
, the bug doesn't occur. - The bug doesn't occur when deleting rows from the middle or top.
- The bug doesn't occur if we don't use the delete-button as target of the
dbc.Tooltip
.
Full runnable code for investigation of the bug:
import dash
from dash import Dash, dcc, html
from dash.exceptions import PreventUpdate
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State, ALL
import json
app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
def make_input(i):
return dbc.Row([dbc.Col(
dcc.Input(id={"index": i, "type": "selection-input"},
type='text',
value='',
placeholder='e.g. c1-c0',
className='w-100'), lg=9)])
def make_input_row(i):
elements = (dbc.Col([html.Div(dbc.Button(children=['Delete'], id={"index": i, "type": 'remove-row-button'}), id=f"remove-row-button-tooltip-wrapper-{i}"),
dbc.Tooltip(
'Delete Row',
target=f"remove-row-button-tooltip-wrapper-{i}",
placement='left')
], lg=1),)
elements = elements + (dbc.Col(make_input(i), lg=4),)
return elements
app.layout = html.Div([html.Div(id='selection-area', children=[
dbc.Row(children=[
*make_input_row(0)
], id={"index": 0, "type": "selection-area-row"}, className="mt-2"),
dbc.Row(children=[
*make_input_row(1)
], id={"index": 1, "type": "selection-area-row"}, className="mt-2"),
dbc.Row(children=[
*make_input_row(2)
], id={"index": 2, "type": "selection-area-row"}, className="mt-2"),
dbc.Row(children=[
*make_input_row(3)
], id={"index": 3, "type": "selection-area-row"}, className="mt-2")
]), dbc.Row(children=[
dbc.Col(lg=1),
dbc.Col(dbc.Button('Add', id='add-row-button', outline=True, color='primary', style={'width': '100%'}), lg=3)
], className="mt-2"), ])
def edit_selection_area(remove_click, selection_elements):
trigger_id = dash.callback_context.triggered[0]['prop_id'].split('.')[0]
if trigger_id == 'add-row-button' and len(selection_elements) < 10:
i = max([channel_element['props']['id']['index'] for channel_element in selection_elements]) + 1 if selection_elements else 0
selection_elements.append(dbc.Row(children=[
*make_input_row(i)
], id={"index": i, "type": "selection-area-row"}, className="mt-2"))
elif 'remove-row-button' in trigger_id and len(selection_elements) > 1 and any(remove_click):
i = json.loads(trigger_id)['index']
selection_elements = [selection_element for selection_element in selection_elements if selection_element['props']['id']['index'] != i]
return selection_elements
@app.callback(
Output('selection-area', 'children'),
[Input('add-row-button', 'n_clicks'),
Input({'type': 'remove-row-button', 'index': ALL}, 'n_clicks')],
[State('selection-area', 'children')]
)
def edit_selection_area_callback(add_click, remove_click, selection_elements):
trigger_id = dash.callback_context.triggered[0]['prop_id']
if "remove-row-button" in trigger_id and not any(remove_click):
print("TRIGGERED A SECOND TIME WHEN CLICKING THE LAST ELEMENT:")
print("trigger_id: ", trigger_id)
print("remove_click: ", remove_click)
print(f"input selection elements: {len(selection_elements)} ", selection_elements)
raise PreventUpdate
print("trigger_id: ", trigger_id)
print("remove_click: ", remove_click)
print(f"input PreventUpdate elements: {len(selection_elements)} ", selection_elements)
children = edit_selection_area(remove_click, selection_elements)
print(f"output PreventUpdate elements: {len(children)} ", children)
print()
return children
if __name__ == "__main__":
app.run_server(debug=True)
Expected behavior
Deletion of the last row containing a delete-button which is target of a dbc.Tooltip
should behave the same way as deletion of rows from the middle or top.
Metadata
Metadata
Assignees
Labels
P3backlogbacklogbugsomething brokensomething brokendash-data-tablerelated to DataTable componentrelated to DataTable component