dash icon indicating copy to clipboard operation
dash copied to clipboard

Pattern Matching Callback Renders All Components When Adding New One

Open andre996 opened this issue 1 year ago • 2 comments

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)

andre996 avatar May 31 '24 15:05 andre996

@andre996 just add prevent_initial_call=True: image

CNFeffery avatar Jun 01 '24 01:06 CNFeffery

To my knowledge this (prevent_initial_call=True) only works when the app first starts. When dynamically adding components to an app, the callback with the pattern matching input will be triggered every time a new component is rendered.

And if the above argument is supposed to prevent this from happening, I can assure you it isn't.

mbworth avatar Jul 13 '24 00:07 mbworth