Dynamic Buttons with Pattern-Matching Callbacks

Hello all,

I am having an issue getting dynamic buttons (with unique index) to send to a pattern-matching call back. The purpose is to have a button for any possible result that comes up in a large database search, and when clicked the pattern-matching callback sends the data from a button with n_clicks=1 to update a separate table.

Currently this works fine when there is a single possible button to click, but if there are multiple results after the first button click, the pattern-match for ALL sends only the clicked button data for all of the buttons (duplicating it across). I have spent 2 days trying to understand how this is possible and am at a loss.

Here is the code I am using, and the frontend. So upon a search, dynamic buttons (in green) linked to the results should send their values.

app.layout = html.Div([
                dbc.Container([
                    html.H5('Ticker Search'),
                    html.P('Search for new tickers to add to the watchlist'),
                    dbc.Input(
                        id="search_input",
                        type='text',
                        placeholder="input type"
                    ),
                    html.Div(id="earnings-output")
                ], style={'align':'center'}),
                dbc.Container([
                    html.H5('Current Watchlist'),
                    html.P(' ', id='watchlist-table'),
                    dcc.Interval(id='interval_component',
                                                    interval=3000,
                                                    n_intervals=0
                                                    ),
                ]),
                dbc.Container([
                    html.H5('Test'),
                    html.P(' ', id='test'),
                    html.P(' ', id='test2')
                ])

            ])

#Callback to auto-refresh the watchlist table every 3000 ms
@app.callback(Output('watchlist-table', 'children'),
              [Input('interval_component', 'n_intervals')])
def watchlist_refresh(n_intervals):
    results = sqlbackend.view_all('watchlist')
    return dbc.Container([
        dbc.Table(
            # Header
            [html.Tr([html.Th('Ticker:Region')]
                     )] +
            # Body
            [html.Tr([
                html.Td(i[0], id='ticker-{}'.format(i[0])),
                dbc.Button('Remove', color="danger", id={'type': 'remove', 'index': i[0]}, n_clicks=0)
                ]) for i in results], bordered=False
        )
    ])

@app.callback(
    Output('test', 'children'),
    [Input({'type': 'add', 'index': ALL}, 'id'),
     Input({'type': 'add', 'index': ALL}, 'n_clicks')],
    # [State({'type': 'add', 'index': ALL}, 'n_clicks')]
)
def add_watchlist(values, clicks):
    # print(values)
    # print(clicks)
    for i, j in enumerate(clicks):
        print(values[i]['index'])
        if j == 1:
            print(values[i]['index'])
            sqlbackend.insert_watchlist(values[i]['index'])
            continue
    return html.Div([
        html.Div('{} = {}'.format(i + 1, value))
        for (i, value) in enumerate(values)
    ])


@app.callback(
    Output('test2', 'children'),
    [Input({'type': 'remove', 'index': ALL}, 'id'),
     Input({'type': 'remove', 'index': ALL}, 'n_clicks')],
    # [State({'type': 'add', 'index': ALL}, 'n_clicks')]
)

#Callback to remove tickers from the watchlist
def remove_watchlist(values, clicks):
    #print(values)
    #print(clicks)
    for i, j in enumerate(clicks):
        if j == 1:
            sqlbackend.delete_watchlist(values[i]['index'])
    return html.Div([
        html.Div('{} = {}'.format(i + 1, value))
        for (i, value) in enumerate(values)
    ])

#Callback to search the earnings db by ticker and display
@app.callback(
    Output("earnings-output", "children"),
    [Input("search_input", "value")],
)
def earnings_search(search):
    results = sqlbackend.view(search)[:5]
    return dbc.Container([
        dbc.Table(
            # Header
            [html.Tr([html.Th('Ticker'),
                      html.Th('Region'),
                      html.Th('Company Name'),
                      html.Th('Earnings Date')]
                     )] +
            # Body
            [html.Tr([
                html.Td(i[0], id='ticker-{}'.format(i[0])),
                html.Td(i[1]),
                html.Td(i[2]),
                html.Td(i[3]),
                dbc.Button('Add', color="success", id={'type': 'add', 'index': '{}:{}'.format(i[0],i[1])}, n_clicks=0)
                ]) for i in results], bordered=False
        )
    ])

if __name__ == "__main__":
    app.run_server(debug=True)

I am not sure if i understand your problem correctly but if you use the pattern-matching ALL. “n_clicks” will give you a list of the number of clicks for each button matching with the pattern. “id” will give you the corresponding id’s for each of the buttons even if they were not clicked yet.

I usually use the “n_clicks” as Input and then dash.callback_context.triggered in the callback to get the information which component was triggered. This always worked for me. I am not sure if there is a better way to do it.

Thanks so much, I’ll give this a shot tonight. I actually wasn’t familiar with callback_context.

I was using n_clicks and id to successfully trigger a dynamic button. The curious thing was that as soon as one of the buttons received a click, all of the incoming ids to the callback were of the clicked button, even though it was the correct amount of them and it was still accurately tracking the amount of respective clicks on each button.