Callback triggered by the wrong button

Hi everyone i have this div in my layout:

                                 html.Div(style={'display':'flex'},
                                          children=[
                                              html.Button('Ajouter champ',
                                                          id='add-fieldtest',
                                                          className='button',
                                                          style={'width':'250px'}),
                                              html.Button('Retirer champ',
                                                          id='remove-fieldtest',
                                                          className='button',
                                                          style={'width':'250px'}),
                                              html.Button('Recherche',
                                                          id='search-buttontest',
                                                          className='button',
                                                          style={'width':'250px'})
                                          ]),

and these two call backs:


@app.callback(
    Output('new-form', 'children'),
    [Input('remove-fieldtest', 'n_clicks'),
     Input('add-fieldtest', 'n_clicks')],
    State('new-form', 'children')
)
def add_removefield(rem_n_clicks, add_n_clicks, children):
    ctx = dash.callback_context
    if not ctx.triggered:
        return children
    else:
        btn_clicked = ctx.triggered[0]['prop_id'].split('.')[0]
        if btn_clicked == 'remove-fieldtest':
            del children[-1]
            return children
        else:
            return children + [replicable_div]



@app.callback(
    Output('footer', 'children'),
    [Input('search-buttontest', 'n_clicks')],
    State('new-form', 'children')
)
def searchtest(n_clicks, children):
    print(get_btn_clicked())
    if n_clicks is not None:
        q = ""
        for i in range(1, len(children)):
            v = ''
            q = q + children[i]['props']['children'][1]['props']['value']
        print(q)
    return html.Div()

The callback searchtest is triggered even when i click on “remove-fieldtest” or “add-fieldtest” buttons, i can’t tell what is wrong ?

When i click the remove and add buttons, both callbacks are triggered.

Hi ghilesfreeman,

it seams to me that the problem you have is related with the if clause:

if n_clicks is not None:

n_clicks by defoult is = 0, then 0 is not None.

Hi eduardo,
n_clicks is none by default, then 1, 2, 3…etc
Even when i remove the if clause, i get the print(get_btn_clicked) telling me that i clicked on the ‘search-buttontest’ even when i click when on one of the two other buttons.

ghilesfreeman
The Dash documentation about html.Button said:

n_clicks ( number ; default 0 ): An integer that represents the number of times that this element has been clicked on.

It`s n_clicks value changing to 1, 2, 3, etc. when you press other buttons ?

Yes.
When i click:

search-buttontest

The callback searchtest is triggered.

When i click:

remove-fieldtest

or

add-fieldtest

Both callbacks add_removefield and searchtest are triggered. If you look at the input values, it makes no sense :anguished:

ghilesfreeman,

I had copy and paste your code and changed the

def searchtest(n_clicks, children):
    print(get_btn_clicked())

For:

def searchtest(n_clicks, children):
    print("Show the n_clicks here:", n_clicks)

And only change its value when the last button is clicked:

That means it`s working as expected !!

I swear to you that it doesn’t work.

here is the get_btn_clicked function

def get_btn_clicked():
    return dash.callback_context.triggered[0]['prop_id'].split('.')[0]

It returns the id of the button that was clicked.
it prints ‘search-buttontest’ everytime.

By the way i created the function specifically to debug the problem. Even when i remove all these verifications, it doesn’t trigger callbacks properly.

You can try this, you’ll see what i mean.

def get_btn_clicked():
    return dash.callback_context.triggered[0]['prop_id'].split('.')[0]


@app.callback(
    Output('footer', 'children'),
    [Input('search-buttontest', 'n_clicks')],
    State('new-form', 'children')
)
def searchtest(n_clicks, children):
    print(get_btn_clicked())
    return html.Div()


@app.callback(
    Output('new-form', 'children'),
    [Input('remove-fieldtest', 'n_clicks'),
     Input('add-fieldtest', 'n_clicks')],
    State('new-form', 'children')
)
def add_removefield(rem_n_clicks, add_n_clicks, children):
    print(get_btn_clicked())
    return children

ghilesfreeman

Probe this code and see that everything related to the button clicked are working well.
Then Try to find where are you missing something.

import dash
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output, State

app = dash.Dash()

app.layout = html.Div([
    
                                 html.Div(style={'display':'flex'},
                                          children=[
                                              html.Button('Ajouter champ',
                                                          id='add-fieldtest',
                                                          className='button',
                                                          style={'width':'250px'}),
                                              html.Button('Retirer champ',
                                                          id='remove-fieldtest',
                                                          className='button',
                                                          style={'width':'250px'}),
                                              html.Button('Recherche',
                                                          id='search-buttontest',
                                                          className='button',
                                                          style={'width':'250px'})
                                          ]),
    html.Div(id= 'new-form'),
    html.Div(id= 'footer'),
    
    
])

@app.callback(
    Output('new-form', 'children'),
    [Input('remove-fieldtest', 'n_clicks'),
     Input('add-fieldtest', 'n_clicks')],
    State('new-form', 'children')
)
def add_removefield(rem_n_clicks, add_n_clicks, children):
    ctx = dash.callback_context
    if not ctx.triggered:
        return children
    else:
        btn_clicked = ctx.triggered[0]['prop_id'].split('.')[0]
        if btn_clicked == 'remove-fieldtest':
            
            return children
        else:
            return children 



@app.callback(
    Output('footer', 'children'),
    [Input('search-buttontest', 'n_clicks')],
    State('new-form', 'children')
)
def searchtest(n_clicks, children):
    print("Show the n_clicks here:", n_clicks)
    if n_clicks is not None:
        print("The 'search-buttontest' has been clicked")
    return html.Div()

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

Good Luck. !! :smiley:

Yes your code work as expected :thinking: That’s why i don’t get why mine is not :anguished:

Ha ha, but I just copy yours. :woozy_face:

I don’t understand why don’t you just use n_clicks value to manage the second callback options.

if n_clicks is >0:
     do something ....

I already tried that :anguished:

Compare both codes (yours and mine) and just analyze the differences.
It is easy to go from mine to yours and step by step you can find where the problem is been generating.

I moved the search-buttontest in another div, and callbacks triggers correctly, but it’s not an ideal solution considering my whole layout and it’s really a mistery still :thinking:

Well it’s clear to me that this is not the solution and also that this is not the problem, if my code works having all the button in the same Div, why do you need to move one from there?
If you can’t understand why the code are working in a different way that you expected, it will be difficult to inprove coding.
You must follow logics steps to find where the problem is.
Starting from my code and adding step by step the differences from yours, you must find and understand where and why your problem is located.

Yes i agree.
I think i get it.

when the add_removefield callback is triggered, it returns a children with the button search-buttontest included in this children, this way, the other callback is triggered.

A newbie question here to try and fix some obvious misunderstanding i have.

I am using the following code (simplified from some of the above code). At issue is the fact that seemingly the remove callback is never triggered despite clicking on the Remove button. Also the n_clicks for the add callback continually go up when I press on the remove button. So wondering what my basic misunderstanding. I understand (i thought) most of the basic structure and usage of dash but this behavior seems to violate my understanding and I hoping this might clarify other VERY weird things I am seeing in my main dash project that I cannot understand.:

import sys

import dash
import dash_html_components as html
from dash.dependencies import Input, Output, State

app = dash.Dash()

app.layout = html.Div([

                                 html.Div(style={'display':'flex'},
                                          children=[
                                              html.Button('Remove',
                                                          id='remove-fieldtest',
                                                          n_clicks=0),
                                              html.Button('Add',
                                                          id='add-fieldtest',
                                                          n_clicks=0),
                                              html.Button('Search',
                                                          id='search-buttontest',
                                                          n_clicks=0)
                                          ]),
    html.Div(children=[html.H5(id= 'new-form')]),
    html.Div(id= 'footer'),


])

@app.callback(
    Output('new-form', 'children'),
    [Input('remove-fieldtest', 'n_clicks')]
)
def removefield(rem_n_clicks):
    sys.stderr.write("rem_n_clicks = {0}\n".format(rem_n_clicks))
    return "remove: " + str(rem_n_clicks)

@app.callback(
    Output('new-form', 'children'),
    [Input('add-fieldtest', 'n_clicks')]
)
def add_field(add_n_clicks):
    sys.stderr.write("add_n_clicks = {0}\n".format(add_n_clicks))
    return "add: " + str(add_n_clicks)


@app.callback(
    Output('footer', 'children'),
    [Input('search-buttontest', 'n_clicks')],
    State('new-form', 'children')
)
def searchtest(n_clicks, children):
    print("Show the search n_clicks here:", n_clicks)
    if n_clicks is not None:
        print("The 'search-buttontest' has been clicked")
    return html.Div()

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

Hi @davez,

You are calling the same id (id= ‘new-form’) from two different callbacks, and this is not allowed.
Just add a new html.H5 with different name for one of the callbacks.
The n_clicks increments in one at every new clicks.