Black Lives Matter. Please consider donating to Black Girls Code today.

Improper behavior with n_clicks in dash 1.7

Hi Dash users :wave:,

I have noticed a modification of behavior when an app uses callbacks with “n_click” since the version 1.7 of Dash. Let’s take the code example after :

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

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.config.suppress_callback_exceptions = True

menu = html.Aside([
    html.Div("CLOSE", id='menu-mobile-close', n_clicks_timestamp=0),
    html.Br(),
    html.Br(),
    dcc.Link('ZOne 1 and TEST 1', href='/zone1/test1', id="z1t1"),
    html.Br(),
    dcc.Link('ZOne 1 and TEST 2"', href='/zone1/test2', id="z1t2"),
    html.Br(),
    dcc.Link('ZOne 2 and TEST 2"', href='/zone2/test1', id="z2t2"),
    html.Br(),
    dcc.Link('ZOne 2 and TEST 2', href='/zone2/test2', id="z2t2"),
    html.Br(),
], id='aside',
    className="")

app.layout = html.Div([
    dcc.Location(id='url', refresh=False),

    html.Div("OPEN SIDEBAR", style={"color": "red"}, id="menu-mobile-open", n_clicks_timestamp=0),

    html.Div(id='page-content')
])


def get_search_bar(zone):
    options = []
    default_value = 0

    if zone == "zone1":
        options = [
            {'label': 'New York City', 'value': 'NYC'},
            {'label': 'Montreal', 'value': 'MTL'}
        ]
        default_value = "NYC"
    elif zone == "zone2":
        options = [
            {'label': 'Paris', 'value': 'PA'},
            {'label': 'Toronto', 'value': 'TO'},
        ]
        default_value = "PA"

    search_bar = html.Nav(
        children=html.Div(
            children=[
                html.Div(
                    children=[
                        dcc.Dropdown(
                            id='select-market-dropdown',
                            options=options,
                            value=default_value,
                            clearable=False,
                            className=""
                        )
                    ]
                )
            ]
        )
    )
    return search_bar


@app.callback(dash.dependencies.Output('page-content', 'children'),
              [dash.dependencies.Input('url', 'pathname')])
def display_page(pathname):
    if pathname is not None:
        selected_zone = pathname.split('/')[1]
        search_bar = get_search_bar(selected_zone)

        return html.Div([
            menu,
            search_bar,
            html.Div(id="main_dashboard")])
    return "home"


@app.callback(
    Output(component_id='main_dashboard', component_property='children'),
    [Input(component_id='select-market-dropdown', component_property='value')],
    [State('url', 'pathname')]
)
def pick_market_in_dropdown(mkt, pathname):
    if mkt == 'NYC' and pathname == "/zone1/test1":
        return "NYC and zone1 and test 1"
    elif mkt == 'NYC' and pathname == "/zone1/test2":
        return "NYC and zone1 and test 2"
    elif mkt == 'MTL' and pathname == "/zone1/test1":
        return "MTL and zone1 and test 1"
    elif mkt == 'MTL' and pathname == "/zone1/test2":
        return "MTL and zone1 and test 2"
    elif mkt == 'PA' and pathname == "/zone2/test1":
        return "PA and zone2 and test 1"
    elif mkt == 'PA' and pathname == "/zone2/test2":
        return "PA and zone2 and test 2"
    elif mkt == 'TO' and pathname == "/zone2/test1":
        return "TO and zone2 and test 1"
    elif mkt == 'TO' and pathname == "/zone2/test2":
        return "TO and zone2 and test 2"
    return mkt


@app.callback(
    Output(component_id='aside', component_property='className'),
    [Input(component_id='menu-mobile-open', component_property='n_clicks_timestamp'),
     Input(component_id='menu-mobile-close', component_property='n_clicks_timestamp')]
)
def pick_market_layout(oclicked, cclicked):
    if int(oclicked) > int(cclicked):
        return 'app-sidebar '
    elif int(cclicked) > int(oclicked):
        return 'app-sidebar hide-sidebar'
    else:
        return 'app-sidebar hide-sidebar'


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

What this little app does is quite simple: the pathname changes the values inside a Dropdown, and the pathname + the dropdown value changes a text.

I have added 2 Div that have n_clicks_timestamp properties to change the className of html.Aside. Their purpose is to show the weird behavior.

With a dash version < 1.7, if I click on a link, then use the dropdown to select a city, then click on another link, the pick_market_in_dropdown() callback is fired and I see in html.Div(id="main_dashboard") the message that is supposed to appear.
However, when I use dash version 1.7 and do the exact same thing (click on a link, use the dropdown, then click on another link), nothing appears in the html.Div(id="main_dashboard"). But if you comment the callback pick_market_layout with the n_clicks_timestamp in Input, it works normally again.

I did not see anything referring to this king of new behavior in the changelog.
Does someone have an explanation? I would really appreciate, thanks! :slight_smile:

Raphourbe

Hi fellows, :wave:

I have not found any solution to this problem yet, so I am still looking for help :slight_smile:

Has anyone been able to reproduce the bug I mention at least?

Many thanks,
Raphourbe

I am also experiencing some issues with callbacks in dash 1.7 (and not in 1.6.1). However, I have not been able to post a MWE yet, as the problems appear in a rather large project at work. And since 1.6.1 works perfectly, further investigation is not the highest priority.