✊🏿 Black Lives Matter. Please consider donating to Black Girls Code today.
⚡️ Concerned about the grid? Kyle Baranko teaches how to predicting peak loads using XGBoost. Register for the August webinar!

Possible Bug: Clientside Callbacks Triggering after Circular Callback

I have been trying out the new circular callback feature in Dash and have encountered an odd relationship between circular callbacks and clientside callbacks when they are sharing the same inputs. When a clientside callback and a normal callback share the same input it looks like they execute simultaneously. When a clientside callback and a circular callback execute the client side callback executes a single time after the circular callback instead of at the same time or executing twice.

Here is the test code I wrote based upon the Advanced Callbacks and Clientside Callbacks documentation. Changing the dropdown should execute the alert twice (once initially, another after the 5 seconds) and changing the slider should execute the alert once (after the 5 seconds). The button acts as a control since it is a normal callback.

I have not seen this documented anywhere and am interested to see if this is an intended feature or a bug.

Thanks!

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

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

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

app.layout = html.Div(
    [
        html.Div(
            'Changing the dropdown should execute the alert twice (once initially, another after the 5 seconds) and '
            'changing the slider should execute the alert once (after the 5 seconds).'),
        dcc.Slider(
            id="slider-circular", min=0, max=3,
            marks={i: str(i) for i in range(4)},
            value=2
        ),
        dcc.Dropdown(
            id="input-circular", options=[{'label': 0, 'value': 0}, {'label': 1, 'value': 1}, {'label': 2, 'value': 2},
                                          {'label': 3, 'value': 3}], value=2
        ),
        html.Div('Clicking the button should execute the alert once immediately'),
        html.Button(id='button', title='Click for Alert'),
        html.Div(id='wrapper', style={'display': 'none'})
    ]
)


@app.callback(
    Output("input-circular", "value"),
    Output("slider-circular", "value"),
    Input("input-circular", "value"),
    Input("slider-circular", "value"),
    prevent_initial_call=True
)
def callback(input_value, slider_value):
    ctx = dash.callback_context
    trigger_id = ctx.triggered[0]["prop_id"].split(".")[0]
    value = input_value if trigger_id == "input-circular" else slider_value
    time.sleep(5)
    return value, value


@app.callback(
    Output("wrapper", "n_clicks"),
    Input("button", "n_clicks"),
    prevent_initial_call=True
)
def callback(button_clicks):
    time.sleep(5)
    return button_clicks


app.clientside_callback(
    '''
    function(value_circular){
        alert(value_circular);
        return 0;
    }
    ''',
    Output('wrapper', 'children'),
    Input('input-circular', 'value'),
    Input('button', 'n_clicks')
)

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

EDIT:
Result from pip list

Brotli               1.0.9
click                8.0.1
colorama             0.4.4
dash                 1.20.0
dash-core-components 1.16.0
dash-html-components 1.1.3
dash-renderer        1.9.1
dash-table           4.11.3
Flask                2.0.1
Flask-Compress       1.10.1
future               0.18.2
itsdangerous         2.0.1
Jinja2               3.0.1
MarkupSafe           2.0.1
pip                  21.1.2
plotly               4.14.3
retrying             1.3.3
setuptools           57.0.0
six                  1.16.0
Werkzeug             2.0.1