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

Dynamic list of dcc components

Hi, I want to create a list for forms that can be extended and reduced dynamically by clicking buttons to do so.

The following code allows the addition/removal of additional divs, however, whenever I choose an option in one of the dropdowns, the chosen option disappears as soon as I choose an option in a different dropdown.

import dash
import dash_core_components as dcc
import dash_html_components as html

step = html.Div(
        children=[
            "Menu:",
            dcc.Dropdown(options=[{'label': v, 'value': v} for v in ['option1', 'option2', 'option3']])
        ])

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

div_list = [step]

app.layout = html.Div(
    children=[
        html.H1(children='Hello Dash'),
        html.Div(children=div_list, id='step_list'),
        html.Button('Add Step', id='add_step_button', n_clicks_timestamp='0'),
        html.Button('Remove Step', id='remove_step_button', n_clicks_timestamp='0')])


@app.callback(
    dash.dependencies.Output('step_list', 'children'),
    [dash.dependencies.Input('add_step_button', 'n_clicks_timestamp'),
     dash.dependencies.Input('remove_step_button', 'n_clicks_timestamp')],
    [dash.dependencies.State('step_list', 'children')])
def add_step(add_ts, remove_ts, div_list):
    add_ts = int(add_ts)
    remove_ts = int(remove_ts)
    if add_ts > 0 and add_ts > remove_ts:
        div_list += [step]
    if len(div_list) > 1 and remove_ts > add_ts:
        div_list = div_list[:-1]
    return div_list


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

Can anybody explain to me what I’m doing wrong?

Thanks a lot!

Hello!

So the issue with what you’re doing currently is you’re exporting a list as the children of a div, so after the function has been triggered, the ‘step_list’ div will look like this:

html.Div(id='step_list',
children=div_list)

If you adjust the output to the drop down directly, you can update the list of options that way, ie:

Output('your_dropdown_list', 'options')
[Input(etc...
def add_step(inputs):
## functions to generate list
return [{'label': v, 'value': v} for v in div_list]

I hope this helps!

Yes, that I know, but I need to add additional menu divs, not just another entry in the dropdown options. This was just an example and the dropdown was just chosen as one type of menu/form element that would be used. In each of these divs (in the step_list) would be several dash core components.

What I am wondering is why the program as it is shown in my original post does not work? If anyone has an idea and possible a solution, that would be great! :slight_smile:

Hello,

I think I’ve seen the issue, you haven’t passed step into the function.

I tried it with this as the function:
def add_step(add_ts, remove_ts, div_list, step=step):

And it worked as I think you want it to.

However this causes an issue where each component added does not have a unique ID.

To get around this, you could define step in the function, and format the id using the n_clicks, or some other such method, such as below.

@app.callback(
    dash.dependencies.Output('step_list', 'children'),
    [dash.dependencies.Input('add_step_button', 'n_clicks_timestamp'),
    dash.dependencies.Input('add_step_button', 'n_clicks'),
     dash.dependencies.Input('remove_step_button', 'n_clicks_timestamp')],
    [dash.dependencies.State('step_list', 'children')])
def add_step(add_ts, clicks, remove_ts, div_list):
    add_ts = int(add_ts)
    remove_ts = int(remove_ts)
    step = html.Div(
        children=[
            "Menu:",
            dcc.Dropdown(id='dropdown_id_{}'.format(clicks), options=[{'label': v, 'value': v} for v in ['option1', 'option2', 'option3']])
    ]) ...

Is this more what you were looking for?

1 Like

I don’t think @AHB 's solution would work. Since in the code

html.Div(children=div_list, id='step_list'),

and

dash.dependencies.Output('step_list', 'children'),

the id is always 'step_list' and you can’t have multiple dropdown value with the same id. For dcc component that are not used as input, like dcc.Graph, dcc.Markdown, you can turn them in a list in the function. But for dcc.Dropdown since it can be used as input, they can’t share the same id.

Hi caiyij,

Thank you for your response.

I am aware of the difficulties of generating multiple IDs for dropdowns, thus the following code snippet:

@app.callback(
    dash.dependencies.Output('step_list', 'children'),
    [dash.dependencies.Input('add_step_button', 'n_clicks_timestamp'),
    dash.dependencies.Input('add_step_button', 'n_clicks'),
     dash.dependencies.Input('remove_step_button', 'n_clicks_timestamp')],
    [dash.dependencies.State('step_list', 'children')])
def add_step(add_ts, clicks, remove_ts, div_list):
    add_ts = int(add_ts)
    remove_ts = int(remove_ts)
    step = html.Div(
        children=[
            "Menu:",
            dcc.Dropdown(id='dropdown_id_{}'.format(clicks), options=[{'label': v, 'value': v} for v in ['option1', 'option2', 'option3']])
    ]) ...

Which would assign a unique dropdown ID based on the number of clicks from the button components.

The callback output references the div itself, and outputs to its children, so the output will add a list of components (‘step’ in this instance) to the div with id step_list, with each component having a unique id.

I have tested both code snippets, the first time did bring up the issue you commented on, which I then fixed with the second code snippet, which functioned as requsted by buugy123.

Regards,

AHB

1 Like

Thanks for your effort guys, but the problem still remains with the posted solution (at least on my computer) that if I choose something (lets say in ‘dropdown_id_1’), the chosen option disappears as soon as I choose something in another dropdown menu.

Hi buugy123,

This issue disappears when you assign a callback to the dropdown.

This is standard dash behavior, as some components remain ‘inactive’ until they are registered as an input, see the below issue:

If you update the app config as below, to allow the use of callbacks from generated components:

app.config['suppress_callback_exceptions']=True

Then create a test div and a basic callback, as the below:

@app.callback(
    dash.dependencies.Output('tester_div', 'children'),
    [dash.dependencies.Input('dropdown_id_1', 'value')])
def a_function(input):
    return html.H1(input)

The callback will fire and dropdown_id_1 won’t auto-clear.

Hope this solves the issue for you!

Hi @AHB, I understand your idea of assigning unique Id, but I tried with your code and still the dropdown clears when choosing a different dropdown. I have added unique id and assigned callbacks. Here’s my complete code.

import dash
import dash_core_components as dcc
import dash_html_components as html

step = html.Div(
        children=[
            "Menu:",
            dcc.Dropdown(options=[{'label': v, 'value': v} for v in ['option1', 'option2', 'option3']])
        ])


app = dash.Dash(__name__)
app.config['suppress_callback_exceptions']=True

div_list = [step]

app.layout = html.Div(
    children=[
        html.H1(children='Hello Dash'),
        html.Div(id='step_list', children=div_list),
        html.Button('Add Step', id='add_step_button', n_clicks_timestamp='0'),
        html.Button('Remove Step', id='remove_step_button', n_clicks_timestamp='0'),
        html.Div(id='tester_div'),
        html.Div(id='tester_div_2')])


@app.callback(
    dash.dependencies.Output('step_list', 'children'),
    [dash.dependencies.Input('add_step_button', 'n_clicks_timestamp'),
    dash.dependencies.Input('add_step_button', 'n_clicks'),
     dash.dependencies.Input('remove_step_button', 'n_clicks_timestamp')],
    [dash.dependencies.State('step_list', 'children')])
def add_step(add_ts, clicks, remove_ts, div_list):
    add_ts = int(add_ts)
    remove_ts = int(remove_ts)
    if add_ts > 0 and add_ts > remove_ts:
        div_list += [html.Div(id='dropdown_id_{}'.format(clicks), children=[
            "Menu:",
            dcc.Dropdown(options=[{'label': v, 'value': v} for v in ['select1', 'select2', 'select3']])
        ])]
    if len(div_list) > 1 and remove_ts > add_ts:
        div_list = div_list[:-1]
    print(div_list)
    return div_list

@app.callback(
    dash.dependencies.Output('tester_div', 'children'),
    [dash.dependencies.Input('dropdown_id_1', 'value')])
def a_function(input):
    return html.H1(input)

@app.callback(
    dash.dependencies.Output('tester_div_2', 'children'),
    [dash.dependencies.Input('dropdown_id_2', 'value')])
def a_function(input):
    return html.H1(input)


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

Hello @caiyij,

You are assigning the unique ID to the div containing the dropdown, not the dropdown component itself, so the dropdowns will not have any unique ID and so will not work in the callbacks.

If you change your add_step function to reflect this:

div_list += [html.Div(
            children=[
                'Menu:',
                dcc.Dropdown(id='dropdown_id_{}'.format(clicks),
                options=[{'label': v, 'value': v} for v in ['select1', 'select2', 'select3']])
            ])]

It should work as expected.

Hope this helps!

4 Likes

Thank you so much @AHB ! It works now. Sorry for the confusion before.

Hi guys, I am using this code to implement a dash app. Unfortunately, I couldn’t find an easy way to extract the input from the dynamically created DCC components to be saved in a variable/database.
I tried dynamically generated callbacks but I thought that I had found a solution with this code:

@app.callback(
Output(‘cache’, ‘children’),
[Input(‘answer{}’.format(i), ‘value’) for i in range(1, number_of_elements + 1)] +
[Input(‘contenuti{}’.format(i), ‘value’) for i in range(1, number_of_elements + 1)]

To use that, I numereted all the elements dynamically generated according to a counter starting from 1. But I didn’t find an easy way to retrieve the current number of elements (number_of_elements) during the callback definition.
Do you have any advice for me?

For anyone reading recently, I was trying to recreate the code above and hit some exceptions. The n_clicks_timestamp attribute is deprecated and although I still find references to it in the docs, I think it has been removed(?). In any case, pattern-matching callbacks address this really nicely.

I’ve rewritten the above example using this new approach for anyone learning in future:


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


app = dash.Dash(__name__)
app.config['suppress_callback_exceptions'] = True


app.layout = html.Div(
    children=[
        html.H1(children='Hello Dash'),
        html.Div(id='step_list', children=[]),
        html.Button('Add Step', id='add_step_button'),  # These could be combined to single ID dict like below too
        html.Button('Remove Step', id='remove_step_button'),
        html.Div(id='tester_div'),
    ]
)


# Use dash.callback_context to know which button was pressed.
# Not sure why we need these on the same callback here, but keeping true to the OP use
@app.callback(
    Output('step_list', 'children'),
    [Input('add_step_button', 'n_clicks'),
     Input('remove_step_button', 'n_clicks')],
    [State('step_list', 'children')])
def add_remove_step(add_clicks, remove_clicks, div_list):
    # Identify who was clicked
    ctx = dash.callback_context
    if not ctx.triggered:
        triggered_id = 'No clicks yet'
    else:
        triggered_id = ctx.triggered[0]['prop_id'].split('.')[0]

    # Act
    if triggered_id == 'add_step_button':
        div_list += [html.Div(
            # Index dropdown's using the next available index
            children=[
                'Menu:',
                dcc.Dropdown(id={'type': 'dropdown', 'index': len(div_list)},
                             options=[{'label': v, 'value': v} for v in ['select1', 'select2', 'select3']])
            ])]
    elif len(div_list) > 0 and triggered_id == 'remove_step_button':
        div_list = div_list[:-1]
    return div_list


# Print all the dropdowns dynamically using a pattern matching callback
# https://dash.plotly.com/pattern-matching-callbacks
@app.callback(
    Output('tester_div', 'children'),
    [Input({'type': 'dropdown', 'index': ALL}, 'value')])
def a_function(input):
    return [html.H1(element) for element in input]


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