N_clicks doesn't update when I click on dynamically loaded button

Hi, I have a multi-page sidebar in my Dash app and when the user clicks on a certain page, they have the option to type in how many rows they want on the screen so that they can type in data.

I want to trigger the callback for this only when they press the “enter” button. However, this ended up not working and after doing some testing, I realized it was due to “n_clicks” staying at “None” even after being clicked. This is where I’m stuck- why doesn’t the number of clicks for the button update when it’s clicked?

I looked at examples online of a dropdown or a button being dynamically loaded or on a different page and it seemed to work fine, so I’m not sure why my code is facing this issue. Below I put relevant code snippets. If you’d like to see more, let me know. I included some commented-out code so you can see what I tried to do. Thanks

Edit: Button callbacks work when the button is in the initial layout, but if the button is created w/in a callback, then it doesn’t work

index.py:

import dash
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
import plotly.express as px
import pandas as pd
from datetime import date
from layouts import sidebar
#import callbacks
from app import app

from apps import chem_app, soil_app, ips_app

app.layout = html.Div([

    dcc.Location(id='url', refresh=False),
    sidebar
    
])

@app.callback(

    Output('sidebar_output', 'children'),
    Input('url', 'pathname')

)

def update_sidebar(pathname):

    if pathname == '/chem':

        return chem_app.layout

    elif pathname == '/ips':

        return [

            ips_app.layout
            
            # dbc.Row([

            #     dbc.Col([
                    
            #         html.Label('# of primary species to add: ', id='IPS_L', style={'font-size':'0.8rem'})
                    
            #         ], width={'size':7}),

            #     dbc.Col([
                    
            #         dcc.Input(
            #                     id='IPS_input',
            #                     type='number',
            #                     placeholder='number',
            #                     style={'width':'4.5rem', 'font-size':'0.85rem'}
            #         )
            #     ]), 

            #     dbc.Col([

            #         html.Button('Enter', id='IPS_Enter', style={'font-size':'0.8rem'})

            #     ])
            
            # ], no_gutters=True),

     
            
        ]

    elif pathname == '/soil':

        return soil_app.layout


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

ips_app.layout:


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

from app import app


layout = html.Div([

    dbc.Row([

                dbc.Col([
                    
                    html.Label('# of primary species to add: ', id='IPS_L', style={'font-size':'0.8rem'})
                    
                    ], width={'size':7}),

                dbc.Col([
                    
                    dcc.Input(
                                id='IPS_input',
                                type='number',
                                placeholder='number',
                                style={'width':'4.5rem', 'font-size':'0.85rem'}
                    )
                ]), 

                dbc.Col([

                    html.Button(id='IPS_Enter', children='Enter', n_clicks=0, style={'font-size':'0.8rem'})

                ])
            
            ], no_gutters=True), 
    
    html.Div(id='IPS_container', style={'height':'35rem'})

])

@app.callback(

    Output('IPS_container', 'children'),
    #Output('IPS_Enter','n_clicks'),
    Input('IPS_Enter', 'n-clicks'),
    #prevent_initial_call=True
    #State('IPS_input', 'value')


)

def add_rows(n_clicks):
    
    string = 'The Button has been pressed {}'.format(n_clicks)

    return [ 

            string,

            dbc.Row([

                dbc.Col([

                    html.Label('Species', id='Species')

                ]),

                dbc.Col([

                    html.Label('Precip', id='Precip')

                ]),

                dbc.Col([

                    html.Label('Surface', id='Surface')

                ]),

                dbc.Col([

                    html.Label('UZ', id='UZ')

                ]),

                dbc.Col([

                    html.Label('LZ', id='LZ')

                ])

            ]),

            #listOfRows

        ]

    # if n_clicks is None:
        
    #     raise PreventUpdate

    #     return None

    # elif n_clicks > 0:

    # if n_clicks is None:
    #         return dash.no_update
    # else:

        # print(rows)

        # listOfRows = [
            
        #     dbc.Row([

        #         dbc.Col([

        #             dcc.Input(
        #                         id='species{}_input'.format(i),
        #                         type='text',
        #                         placeholder='Species',
        #                         style={'width':'4.9rem'}
        #             )

        #         ]),

        #         dbc.Col([

        #             dcc.Input(
        #                         id='precip{}_input'.format(i),
        #                         type='number',
        #                         placeholder='number',
        #                         style={'width':'2.45rem'}
        #             )

        #         ]),

        #         dbc.Col([

        #             dcc.Input(
        #                         id='surface{}_input'.format(i),
        #                         type='number',
        #                         placeholder='number',
        #                         style={'width':'2.45rem'}
        #             )

        #         ]),

        #         dbc.Col([

        #             dcc.Input(
        #                         id='uz{}_input'.format(i),
        #                         type='number',
        #                         placeholder='number',
        #                         style={'width':'2.45rem'}
        #             )

        #         ]),

        #         dbc.Col([

        #             dcc.Input(
        #                         id='lz{}_input'.format(i),
        #                         type='number',
        #                         placeholder='number',
        #                         style={'width':'2.45rem'}
        #             )

        #         ])

        #     ])
            
        #     for i in range(rows)
            
        # ]

   

Hi @vepr0221

The callbacks inputs “id” needs to be in the layout when the app starts, if not you’ll recieve an error message telling that the “id” doesnt exist.

To use "id"s that are generated dynamically you need to use pattern matching callbacks:

https://dash.plotly.com/pattern-matching-callbacks

Thank you for the response @Eduardo ! And true, I do recall reading about that in the docs. However, a similar set-up seems to work in this app (this was in a dash app recipe guide and I slightly modified it to make it more similar to my own situation):

multi-page-app:

index.py:

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

from app import app
from apps import app1, app2


app.layout = html.Div([
    dcc.Location(id='url', refresh=False),
    html.Div(id='page-content')
])


@app.callback(
    
    Output('page-content', 'children'),
    Input('url', 'pathname')

)
def display_page(pathname):
    if pathname == '/apps/app1':
         return app1.layout
    elif pathname == '/apps/app2':
         return app2.layout
    else:
        return '404'

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

app1.py:

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

from app import app

layout = html.Div([
    html.H3('App 1'),
    html.Button(id='IPS_Enter', children='Enter'),
    html.Div(id='app-1-display-value'),
    dcc.Link('Go to App 2', href='/apps/app2')
])


@app.callback(
    Output('app-1-display-value', 'children'),
    Input('IPS_Enter', 'n_clicks')
)
def display_value(value):
    return 'You have selected "{}"'.format(value)

I’m confused on why this code would work but mine wouldn’t since they both seem to be similar situations, unless I’m missing something

And just as an aside- the “sidebar_output” in the index.py callback is defined in another part of the app- it’s why I have “from layouts import sidebar” at the top; since I defined the sidebar code in layouts.py

Hi @vepr0221 , it looks like you are using n-clicks instead of n_clicks in your callback. Changing to Input(<id>, "n_clicks") should fix your callback.

2 Likes

Hi @benn0, now it’s dynamically updating. I can’t believe I missed that. Thank you so much for the help, I really appreciate it!

1 Like