Patch generates false callback trigger

Example below:

  1. Click on the link (first element) - both n_clicks and number of callback triggers go up by 1.
  2. Click on the button (patches the text of the link) - callback is triggered but n_clicks is the same.

As a result - I get false callback triggers and don’t have an obvious way to mask them other than a dcc.store storing past n_clicks values.

The code:

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

app = Dash()

app.layout = [
    html.Div(html.A(children='I am a link inside a div A', id='htmla', href='#', n_clicks=0), id='htmldiv'),  # TODO MAKE HOVER
    html.Button('Toggle link A <-> B', id='button', n_clicks=0),
    html.Br(),
    html.P(children='Link clicks:'),
    html.P(children=0, id='link_clicks'),
    html.P(children='Link n_clicks:'),
    html.P(children=0, id='n_clicks'),
]


@callback(
    Output('link_clicks', 'children'),
    Output('n_clicks', 'children'),
    Input('htmla', 'n_clicks'),
    State('link_clicks', 'children'),
    prevent_initial_call=True,
)
def update_link_clicks(n_clicks, prev_clicks):
    return prev_clicks + 1, n_clicks


@callback(
    Output('htmldiv', 'children'),
    Input('button', 'n_clicks'),
    State('htmldiv', 'children'),
    prevent_initial_call=True,
)
def update_link_text(_, current_children):
    patched_link = Patch()
    if current_children['props']['children'] == 'I am a link inside a div A':
        patched_link['props']['children'] = 'I am a link inside a div B'
    else:
        patched_link['props']['children'] = 'I am a link inside a div A'
    return patched_link


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

Why do you expect something different? Maybe Iḿ missing something here…

I guess this is just an example, but in this case the use of Patch() does not have any benefit as you are using the State()of the Output()already.

I’m expecting the callback to fire only when the link is clicked = when n_clicks grows.

Right now it fires also when the link text changes and n_clicks remains the same.

You are right about Patch not being necessary in the specific example, but it is there in the example. In my larger code it is needed.

I think this is caused by you updating the children of htmldiv instead of updating the children of htmla.

This is a workaround which behaves as you expect:

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

app = Dash()

app.layout = [
    html.Div(
        html.A(
            children=['I am a link inside a div A'],
            id='htmla',
            href='#',
            n_clicks=0
        ),
        id='htmldiv'
    ),  # TODO MAKE HOVER
    html.Button('Toggle link A <-> B', id='button', n_clicks=0),
    html.Br(),
    html.P(children='Link clicks:'),
    html.P(children=0, id='link_clicks'),
    html.P(children='Link n_clicks:'),
    html.P(children=0, id='n_clicks'),
]


@callback(
    Output('link_clicks', 'children'),
    Output('n_clicks', 'children'),
    Input('htmla', 'n_clicks'),
    State('link_clicks', 'children'),
    prevent_initial_call=True,
)
def update_link_clicks(n_clicks, prev_clicks):
    return prev_clicks + 1, n_clicks


@callback(
    Output('htmla', 'children'),
    Input('button', 'n_clicks'),
    State('htmla', 'children'),
    prevent_initial_call=True,
)
def update_link_text(_, current_children):
    patched = Patch()
    if current_children == ['I am a link inside a div A']:
        patched.clear()
        patched.append('I am a link inside a div B')
    else:
        patched.clear()
        patched.append('I am a link inside a div A')
    return patched


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

BTW, as I mentioned earlier, you do not need to use Patch() in your example. If you change your example to not using the Patch()method, you will observe the same behavior- so it’s not related to Patch()as your topic title suggests.

@AIMPED First, thank you for checking this out.

I confirmed that your example works well.
Also, the simpler example of updating the children of htmla directly works as well, without patch:

@callback(
    Output('htmla', 'children'),
    Input('button', 'n_clicks'),
    State('htmla', 'children'),
    prevent_initial_call=True,
)
def update_link_text(_, current_children):
    if current_children == 'I am a link inside a div A':
        return 'I am a link inside a div B'
    else:
        return 'I am a link inside a div A'

The problem is that my actual code is more complex and I need to use patch. Specifically, the htmldiv is a simplication for an entire dbc.Table and I’m patching a specific column of it to change the classname of each cell. The user is un/selecting columns and I want to un/grey them out.

So the actual changes (running in a loop over the column) look like:

patched_table['props']['children'][1]['props']['children'][row]['props']['children'][col]\
                ['props']['children']['props']['className'] = new_classname

And this is where I’m stuck.