plotly / dash

Data Apps & Dashboards for Python. No JavaScript Required.
https://plotly.com/dash
MIT License
20.82k stars 2.01k forks source link

[BUG]: Pattern Matching Callback Renders All Components When Adding New One. #2872

Open andre996 opened 4 weeks ago

andre996 commented 4 weeks ago

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

dash                      2.13.0
dash-core-components      2.0.0
dash-enterprise-libraries 1.3.0
dash-html-components      2.0.0
dash_mantine_components   0.14.3
dash-table                5.0.0

Describe the bug

I'm using a sample app from the documentation that uses a pattern matching callback (MATCH). I added a dcc.Loading component, and the problem is that when I add a new pair of components, all the old components get refreshed too.

Expected behavior

When I add a new component, only the new component should be rendered.

Screenshots

logs

Code

from dash import Dash, dcc, html, Input, Output, State, MATCH, Patch, callback

app = Dash(__name__)

app.layout = html.Div([
    html.Button("Add Filter", id="dynamic-add-filter-btn", n_clicks=0),
    html.Div(id='dynamic-dropdown-container-div', children=[]),
])

@callback(
    Output('dynamic-dropdown-container-div', 'children'),
    Input('dynamic-add-filter-btn', 'n_clicks')
    )
def display_dropdowns(n_clicks):
    patched_children = Patch()
    new_element = html.Div([
        dcc.Dropdown(
            ['NYC', 'MTL', 'LA', 'TOKYO'],
            id={
                'type': 'city-dynamic-dropdown',
                'index': n_clicks
            }
        ),
        dcc.Loading(
            # id={"type": "file-loading", "index": idx},
            type="default",
            children=html.Div(
                id={
                    'type': 'city-dynamic-output',
                    'index': n_clicks
                }
        ))
    ])
    patched_children.append(new_element)
    return patched_children

@callback(
    Output({'type': 'city-dynamic-output', 'index': MATCH}, 'children'),
    Input({'type': 'city-dynamic-dropdown', 'index': MATCH}, 'value'),
    State({'type': 'city-dynamic-dropdown', 'index': MATCH}, 'id'),
)
def display_output(value, id):
    import time, random
    sleep_time = random.randint(1, 5)
    time.sleep(sleep_time)
    print(html.Div(f"Dropdown {id['index']} = {value}"))
    return html.Div(f"Dropdown {id['index']} = {value}")

if __name__ == '__main__':
    app.run(debug=True)
CNFeffery commented 4 weeks ago

@andre996 just add prevent_initial_call=True: image