Callback won't work on callback chain

Context

I’ve create dropdown input which chain together using callbacks.

  1. When customer dropdown is selected, it’ll remove purchase and engage dropdown value
  2. When purchase dropdown is selected, it’ll update engage dropdown option depend on purchase value
  3. When engage dropdown is selected, it’ll update purchase dropdown option depend on engage value

The problem is when I select customer dropdown, purchase value got clear value but it doesn’t update engage dropdown options.

If I remove engage dropdown callback, it will work just fine.

Question

Is this a bug or my code are wrong ?

Sample code

# -*- coding: utf-8 -*-
import dash
import dash_html_components as html
import dash_core_components as dcc
import time

from dash.dependencies import Input, Output, State

app = dash.Dash(__name__)

app.layout = html.Div(
    children=[
        html.Div([
            html.Label('Customer', className='label'),
            html.Div([
                dcc.Dropdown(
                    options=[
                        {'label': 'Total', 'value': 'all'},
                        {'label': 'Segment', 'value': 'segment'},
                        {'label': 'Journey', 'value': 'journey'},
                    ],
                    value='all',
                    clearable=False,
                    id='customer_select',
                ),
            ], className='control'),
        ], className='field'),

        # segment purchase
        html.Div(
            [
                html.Label('Purchase', className='label'),
                html.Div([
                    dcc.Dropdown(
                        options=[
                            {'label': 'Purchaser', 'value': 'purchaser'},
                            {'label': 'Non-purchaser', 'value': 'non_purchaser'},
                        ],
                        clearable=True,
                        id='purchase_select',
                    ),
                ], className='control'),
            ],
            className='field',
            id='purchase_select_field',
            # style={'display': 'none'},
        ),

        # segment engager
        html.Div(
            [
                html.Label('Engage', className='label'),
                html.Div([
                    dcc.Dropdown(
                        options=[
                            {'label': 'Engager', 'value': 'engager'},
                            {'label': 'Non-engager', 'value': 'non_engager'},
                        ],
                        clearable=True,
                        id='engage_select',
                    ),
                ], className='control'),
            ],
            className='field',
            id='engage_select_field',
            # style={'display': 'none'},
        ),
    ],
)


@app.callback(
    Output('engage_select', 'options'),
    [
        Input('purchase_select', 'value'),
    ],
)
def limit_engage(
    purchase_val,
):
    print('limit engage got call ++++++')
    options = [
        {'label': 'Engager', 'value': 'engager'},
        {'label': 'Non-engager', 'value': 'non_engager'},
    ]
    if purchase_val:
        print('has purchase value ----------------')
        options = [
            {'label': 'Engager', 'value': 'engager'},
        ]
    return options


@app.callback(
    [
        Output('purchase_select', 'options'),
    ],
    [
        Input('engage_select', 'value'),
    ],
)
def limit_purchase(
    engage_val,
):
    if engage_val == 'non_engager':
        options = []
    else:
        options = [
            {'label': 'Purchaser', 'value': 'purchaser'},
            {'label': 'Non-purchaser', 'value': 'non_purchaser'},
        ]

    return (
        options,
    )


@app.callback(
    [
        Output('purchase_select', 'value'),
        Output('engage_select', 'value'),
    ],
    [
        Input('customer_select', 'value')
    ],
)
def toggle_display(customer_sel):
    # blank value to clear field value
    blank_value = [None, None]

    return tuple(blank_value)


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

App page and callback graph

If you want the options of engage_select to be reset, you need to pass options as the output compoent vice value. This is based on your problem statement that “it doesn’t update the engage dropdown options”.

Out of curiosity, why are you returning a tuple in the toggle_display callback? Why not just return blank_value?

But I did pass engage_select options as an output. (First callback)

It’s a simplify version of function, normally I’ll have style as an output too and It’ll return as tuple(blank_value + style_value)

The first callback is only triggered by `purchase_select; thus it will not be triggered by the customer dropdown. Seems like you may want to have multiple inputs to the first callback to update its output based on what triggered it.

The engage_select callback will be triggered by customer_select callback. I would like to point out a few things here.

Using this code

  1. engage_select callback does got call when customer_select is change by callback chain from customer_select callback to engage_select callback, confirm by using print statement.

  2. If I remove purchase_select callback and keep the rest unchanged, code will work as intended. (purchase_select options will change when I change customer_select value)

I was finally able to run your code and I can’t seem to understand why the enage_select won’t update…the correct output is being created but the dropdown is failing to update. Not sure what is going on but i’ll try to figure this out.