How to handle dynamic created input fields

I’m trying to interact with with dynamic created input fields which are created when changes in radioitems, however, when I try to interact with these variables later as a State, I always get the following error:

“A nonexistent object was used in an State of a Dash callback. The id of this object is Montréal and the property is value.”

I have created this reproducible example:

import dash
import dash_table
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import dash_bootstrap_components as dbc
from dash.exceptions import PreventUpdate

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

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

all_options = {
    'America': ['New York City', 'San Francisco', 'Cincinnati'],
    'Canada': [u'Montréal', 'Toronto', 'Ottawa']
}


def generate_inputs(shortName):
    return dbc.Input(id=str(shortName),
                     placeholder=str(shortName),
                     type='text',
                     style={"margin-bottom": "5px"})


app.layout = html.Div([
    dcc.RadioItems(
        id='countries-radio',
        options=[{'label': k, 'value': k} for k in all_options.keys()],
        value='America'
    ),
    dbc.Button(id='btn-scrape',
                           children=["Download  ", html.I(className="fa fa-download mr-1")],
                           color="primary",
                           className="mt-1"
                           ),
    html.Hr(),

    html.Div(id='display-inputs', children=[generate_inputs(i) for i in all_options['America']]),

    html.Hr(),
    dbc.Col([
        dcc.Loading(
            id='loading-1',
            type='circle',
            children=[
                dash_table.DataTable(
                    id='datatable-paging',
                    data=[{}],
                    editable=True,
                    style_cell={
                        'whiteSpace': 'normal',
                        'lineHeight': 'auto',
                        'height': 'auto',
                        'fontSize': 12},
                    page_current=0,
                    page_size=10,
                    page_action='native'),
                dcc.Download(id='download-table')
            ]),
    ])
])

@app.callback(
    Output('display-inputs', 'children'),
    Input('countries-radio', 'value'),
)
def set_cities_children(scraping_setting):

    return dbc.Col(children=[generate_inputs(i) for i in all_options[scraping_setting]], className='placeholder')


'''
App callback: Scraping data from eurlex using the XmlScraper Class
'''
@app.callback(
    Output('datatable-paging', 'data'),
    [Input('btn-scrape', 'n_clicks')],
    [State('countries-radio', 'value'),
    State('New York City', 'value'),
    State('San Francisco', 'value'),
    State('Cincinnati', 'value'),
    State('Montréal', 'value'),
    State('Toronto', 'value'),
    State('Ottawa', 'value')]
)
def update_output(n_clicks, scraping_value, ny,sf,ci,mo,to,ot ):
    if n_clicks is None:
        raise PreventUpdate
    else:
        if 'America' in scraping_value:
            print(ny,sf,ci)

        elif 'Canada' in scraping_value:
            print(mo,to,ot)

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

Hi @plotly_enthutiast and welcome to the community!

You just need to set prevent_initial_callbacks=True and suppress_callback_exceptions=True the former will handle the nonexistent object error as it prevents the callback from firing when the app is first loaded and the later will handle the ID not found error.

Just add them in your app definition like this:

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

Yes that works initially when starting the application until I press the download button, then the application does again give me the same error because it cannot handle the non-existing input fields.

Do you know a solution for that as well @atharvakatre?

@plotly_enthutiast
There is an easy way to do what you are trying to achieve here using Pattern Matching Callbacks.

Instead of adding all the dynamic components in the callback you can use the pattern-matching callback selectors.

Here is the modified code:

import dash
import dash_table
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State, ALL
import dash_bootstrap_components as dbc
from dash.exceptions import PreventUpdate

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

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

all_options = {
    'America': ['New York City', 'San Francisco', 'Cincinnati'],
    'Canada': [u'Montréal', 'Toronto', 'Ottawa']
}


def generate_inputs(shortName):
    return dbc.Input(id={
                        'type': 'dynamic-textbox',
                        'index': shortName
                    },
                     type='text',
                     placeholder=str(shortName),
                     style={"margin-bottom": "5px"})


app.layout = html.Div([
    dcc.RadioItems(
        id='countries-radio',
        options=[{'label': k, 'value': k} for k in all_options.keys()],
        value='America'
    ),
    dbc.Button(id='btn-scrape',
                           children=["Download  ", html.I(className="fa fa-download mr-1")],
                           color="primary",
                           className="mt-1"
                           ),
    html.Hr(),

    html.Div(id='display-inputs', children=[generate_inputs(i) for i in all_options['America']]),

    html.Hr(),
    dbc.Col([
        dcc.Loading(
            id='loading-1',
            type='circle',
            children=[
                dash_table.DataTable(
                    id='datatable-paging',
                    data=[{}],
                    editable=True,
                    style_cell={
                        'whiteSpace': 'normal',
                        'lineHeight': 'auto',
                        'height': 'auto',
                        'fontSize': 12},
                    page_current=0,
                    page_size=10,
                    page_action='native'),
                dcc.Download(id='download-table')
            ]),
    ])
])

@app.callback(
    Output('display-inputs', 'children'),
    Input('countries-radio', 'value'),
)
def set_cities_children(scraping_setting):
    return dbc.Col(children=[generate_inputs(i) for i in all_options[scraping_setting]], className='placeholder')

'''
App callback: Scraping data from eurlex using the XmlScraper Class
'''
@app.callback(
    Output('datatable-paging', 'data'),
    [Input('btn-scrape', 'n_clicks')],
    State({'type': 'dynamic-textbox', 'index': ALL}, 'value'),
)
def update_output(n_clicks, value):
    if n_clicks is None:
        raise PreventUpdate
    else:
        print(value)

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

Thank you very much. This was exactly what i needed.

1 Like