Pattern matching Callback with ALL in output. Is this syntax seriously correct?

Hi there,

simple code, there’s a list of labels and if the list of labels updates, the options for all dropdown menus should update accordingly. I first show you how I intuitively wrote the code and tell you why:

@app.callback(
    Output({'type': 'add-label-radio-input', 'index': ALL}, 'options'),
    Input('label-list', 'children'),
)
def update_label_options(label_list, old_options):
    """updates  options for the label selection when the label list changes.
    The first element of the label list ist the label list header. Therefore the
    list comprehension `labels=...` starts at label_list index 1.

    Args:
        label_list: children from the label list
        current_dataset: current dataset info and data

    Returns:
        updated radio options.
    """
    if not dash.callback_context.triggered[0]['value']:
        raise dash.exceptions.PreventUpdate
    # the props children stuff is getting the value from html.H6 element of the
    # label card.
    labels = [list_item['props']['children'][1]['props']['children']
              for list_item in label_list[1:]] 
    return [{"label": label, "value": label} for label in labels]

My thinking was, that if I have a dynamically generated elements of which I want to update a dynamic number, ALL is obviously what I want to have. ALL to me implies that I want the same output to go to all these components. Therefore I thought that I can just return the desired output, in this case a list of options as shown, and dash will deliver that to all components that the pattern matching matched.

That doesn’t work though… Dash requires a list of outputs equal to the number of matched components that need to be updated. I find that a bit odd.

This is how I got it to work:

@app.callback(
    Output({'type': 'add-label-radio-input', 'index': ALL}, 'options'),
    Input('label-list', 'children'),
    State({'type': 'add-label-radio-input', 'index': ALL}, 'options')
)
def update_label_options(label_list, old_options):
    """updates  options for the label selection when the label list changes.
    The first element of the label list ist the label list header. Therefore the
    list comprehension `labels=...` starts at label_list index 1.

    Args:
        label_list: children from the label list
        current_dataset: current dataset info and data

    Returns:
        updated radio options.
    """
    if not dash.callback_context.triggered[0]['value']:
        raise dash.exceptions.PreventUpdate
    # the props children stuff is getting the value from html.H6 element of the
    # label card.
    labels = [list_item['props']['children'][1]['props']['children']
              for list_item in label_list[1:]]
    options = [{"label": label, "value": label} for label in labels]
    return [options for why_though in range(len(old_options))]

I added the State of the matched components just to find out how many there are and create a list of outputs (all equal) of the required length. This seems to me like bad code ^^
Is this how I am supposed to use ALL matched Output?
Is there a reason for why that is so? Use cases where you want to match ALL outputs and then send different values to these outputs?! I was not able to infer the order in which matched outputs appear in the list as all got the same value anyway.

I would really appreciate some feedback/input on this. Thanks in advance!

cheers,

ZBN

I also encountered this and got a little confused. Here is how I believe it works under the hood, though someone more familiar with pattern matching callbacks should correct me if I’m wrong.

If you provide a single value that is not a list, then that value will be passed to all matching components as we’d expect. However, if we pass a list, I believe Dash is interpreting it as “we will index this list and pass each index to its corresponding component”. I.e. the first item of the list is going to the first item, the second to the second item, and so on.

Since you are returning a list of options, Dash wants to provide each option as the output value. You’ll have to provide a list of list of options to accomplish this.

I get around this a slightly different way:

from dash import callback_context

@callback(
    output=dict(
        options=Output({'type': 'input', 'index': ALL}, 'options')
    ),
    ... # remaining inputs and state variables
)
def method(inputs):
    outputs = len(callback_context.outputs_grouping['options'] # provides the full list of output items under 'options' on the current page of the dash app
    ... # the rest of your code
    return {'options': [options for _ in range(outputs)]}

Hi,

thanks for shedding some light into this, that makes a lot of sense. Your solution is definitely better, I’ll change my code accordingly!

I’d like the docs about Pattern matching callbacks to mention this, that would be really nice. There’s no example for an ALL matching Output afaik.

1 Like

Perhaps @adamschroeder or @chriddyp could help get some of this information in the docs.

One additional thing that I just found out!
The return value must look like this:

{'options': [options for _ in range(outputs)]}

because the output grouping expects a dictionary with the key options, and the value being a list with options for each output.
Hm, I guess I have to get used to the output groupings syntax, hehe.

Thanks for your help!

1 Like

Thank you @ZBN_FREEGHOST for your question and thank you @raptorbrad for the solution and suggestion. I shared your documentation recommendation with our technical writer.

Thank you,

Thank you for pointing that out. I updated my answer to reflect the change.