Black Lives Matter. Please consider donating to Black Girls Code today.
Dash HoloViews is now available! Check out the docs.

Problem with callbacks in different tabs

I’ve got two tabs in my applications and each one of them has a graph, a dropdown and a callback that updates the graph. The problem is that when I go to the second tab and try to update the graph, I’m automatically taken to the first tab which is the default and then I have to click on the second tab again to see the chages. Why is this happening? Am I doing something wrong or that’s a bug in Dash? Any help will be appreciated. Thanks.

My code:

import dash
import dash_html_components as html
import dash_core_components as dcc
import pandas as pd
from dash.dependencies import Input, Output
import plotly.graph_objs as go


app = dash.Dash()

df = pd.read_csv('gapminderDataFiveYear.csv')


year_options = []
for year in df['year'].unique():
    year_options.append({'label':str(year),'value':year})

# Create a Dash layout
app.layout = html.Div([
    html.Div(
        html.H1('My Dashboard')
    ),
    dcc.Tabs(id="tabs", value='Tab1', children=[
        dcc.Tab(label='Tab 1', id='tab1', value='Tab1', children =[
            dcc.Graph(id='graph-1'),
            dcc.Dropdown(id='year-picker-1', options=year_options, value=df['year'].min())


        ]),

        dcc.Tab(label='Tab 2', id='tab2', value= 'Tab2', children=[
            dcc.Graph(id='graph-2'),
            dcc.Dropdown(id='year-picker-2', options=year_options, value=df['year'].min())


        ])
    ])
])


@app.callback(Output('graph-1', 'figure'),
              [Input('year-picker-1', 'value')])
def update_figure_1(selected_year):
    filtered_df = df[df['year'] == selected_year]
    traces = []
    for continent_name in filtered_df['continent'].unique():
        df_by_continent = filtered_df[filtered_df['continent'] == continent_name]
        traces.append(go.Scatter(
            x=df_by_continent['gdpPercap'],
            y=df_by_continent['lifeExp'],
            text=df_by_continent['country'],
            mode='markers',
            opacity=0.7,
            marker={'size': 15},
            name=continent_name
        ))

    return {
        'data': traces,
        'layout': go.Layout(
            xaxis={'type': 'log', 'title': 'GDP Per Capita'},
            yaxis={'title': 'Life Expectancy'},
            hovermode='closest'
        )
    }


@app.callback(Output('graph-2', 'figure'),
              [Input('year-picker-2', 'value')])
def update_figure_1(selected_year):
    filtered_df = df[df['year'] == selected_year]
    traces = []
    for continent_name in filtered_df['continent'].unique():
        df_by_continent = filtered_df[filtered_df['continent'] == continent_name]
        traces.append(go.Scatter(
            x=df_by_continent['gdpPercap'],
            y=df_by_continent['lifeExp'],
            text=df_by_continent['country'],
            mode='markers',
            opacity=0.7,
            marker={'size': 15},
            name=continent_name
        ))

    return {
        'data': traces,
        'layout': go.Layout(
            xaxis={'type': 'log', 'title': 'GDP Per Capita'},
            yaxis={'title': 'Life Expectancy'},
            hovermode='closest'
        )
    }



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

I think it is a Div container issue.
Right now you have a single html.Div containing all your tabs and figues etc.
In my app I have structured it somewhat like this:

app.layout = html.Div([
    html.Div(
        html.H1('My Dashboard')
    ),
    dcc.Tabs(id="tabs", value='Tab1', children=[
        
         dcc.Tab(label='Tab 1', id='tab1', value='Tab1', children =[
            html.Div(id='graph-1'),
            dcc.Dropdown(id='year-picker-1', options=year_options, value=df['year'].min())


        ]),

         dcc.Tab(label='Tab 2', id='tab2', value= 'Tab2', children=[
            html.Div(id='graph-2'),
            dcc.Dropdown(id='year-picker-2', options=year_options, value=df['year'].min())


        ])
    ])
])

@app.callback(
   Output['graph-1', 'children']
   [Input('year-picker-1', 'value')]
def __your update figure stuff__
   graphs = []
   'stuff you do to make plot'
   graphs.append(html.Div(dcc.Graph(id=blah, figure='your plot here')))
   return graphs

'same thing with no. 2'

I am not 100% sure this will help, but I saw that it is a difference from what I am familiar with. :slightly_smiling_face:

1 Like

Hi, thanks for trying to help. I tried what you suggested but the problem is still there. :confused:

Here’s my code:

import dash
import dash_html_components as html
import dash_core_components as dcc
import pandas as pd
from dash.dependencies import Input, Output
import plotly.graph_objs as go


app = dash.Dash()

df = pd.read_csv('gapminderDataFiveYear.csv')


year_options = []
for year in df['year'].unique():
    year_options.append({'label':str(year),'value':year})

# Create a Dash layout
app.layout = html.Div([
    html.Div(
        html.H1('My Dashboard')
    ),
    dcc.Tabs(id="tabs", value='Tab2', children=[
        dcc.Tab(label='Tab 1', id='tab1', value='Tab1', children =[
            html.Div(id='graph-1'),
            dcc.Dropdown(id='year-picker-1', options=year_options, value=df['year'].min())


        ]),

        dcc.Tab(label='Tab 2', id='tab2', value= 'Tab2', children=[
            html.Div(id='graph-2'),
            dcc.Dropdown(id='year-picker-2', options=year_options, value=df['year'].min())


        ])
    ])
])


@app.callback(Output('graph-1', 'children'),
              [Input('year-picker-1', 'value')])
def update_figure_1(selected_year):
    graphs = []
    filtered_df = df[df['year'] == selected_year]
    traces = []
    for continent_name in filtered_df['continent'].unique():
        df_by_continent = filtered_df[filtered_df['continent'] == continent_name]
        traces.append(go.Scatter(
            x=df_by_continent['gdpPercap'],
            y=df_by_continent['lifeExp'],
            text=df_by_continent['country'],
            mode='markers',
            opacity=0.7,
            marker={'size': 15},
            name=continent_name
        ))
    graphs.append(html.Div(dcc.Graph(id = 'mygr1',figure={
        'data': traces,
        'layout': go.Layout(
            xaxis={'type': 'log', 'title': 'GDP Per Capita'},
            yaxis={'title': 'Life Expectancy'},
            hovermode='closest'
        )
    })))
    return graphs



@app.callback(Output('graph-2', 'children'),
              [Input('year-picker-2', 'value')])
def update_figure_2(selected_year):
    graphs=[]
    filtered_df = df[df['year'] == selected_year]
    traces = []
    for continent_name in filtered_df['continent'].unique():
        df_by_continent = filtered_df[filtered_df['continent'] == continent_name]
        traces.append(go.Scatter(
            x=df_by_continent['gdpPercap'],
            y=df_by_continent['lifeExp'],
            text=df_by_continent['country'],
            mode='markers',
            opacity=0.7,
            marker={'size': 15},
            name=continent_name
        ))

        graphs.append(html.Div(dcc.Graph(id='mygr2', figure={
        'data': traces,
        'layout': go.Layout(
            xaxis={'type': 'log', 'title': 'GDP Per Capita'},
            yaxis={'title': 'Life Expectancy'},
            hovermode='closest'
        )
    })))

    return graphs


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

I would put everything into the mother-Div of each of your dcc.Tab.

As example, I structure my stuff like this

tab1_layout=[
   html.Div(id='graph-1'),
   dcc.Dropdown(id,options,value)
]

tab2_layout=[
   html.Div(id='graph-2'),
   dcc.Dropdown(id,options,value)
]

app.layout = html.Div(
   children=[
      dcc.Tabs(
         id='tabs', value=1, children=[
            dcc.Tab(label='Tab1', value=1),
            dcc.Tab(label='Tab2', value=2),
            ]
      ),
   html.Div(id='tab-output')
   ]
)

@app.callback(
	Output('tab-output', 'children'),
	[Input('tabs', 'value')])
def show_content(value):
	if value == 1:
		return tab1_layout
	elif value == 2:
		return tab2_layout
	else:
		html.Div()

So now all your tab layout is a list of dash-html and dash-core components, which in your callback are all placed in the same ‘mother-Div’.

1 Like

There were recently come tab bug fixes in dash-core-components==0.27.2: https://github.com/plotly/dash-core-components/blob/master/CHANGELOG.md#0272

Are you using this updated version?

I am now and the problem seems to be resolved.

I updated to the most recent dash-core-components. The problem still persists only when I use pre-populate the tabs with the children similar to @rory. However, when I assign the tabs’ children to a callback function like @Blaceus suggested, it is fine. I would rather have the children be pre-populated because this allows someone using the dashboard to have whatever they did on each tab ‘saved’ when they move around to other tabs.

Same here as @nicolascage

I am taken to the default tab whenever I use a callback in the second one. I tried @Blaceus strategy of callbacks but won’t work for me. Did you guys found a solution for this?

I tried updating all dash components but the issue persists

1 Like

I read that @nicolascage made it work using my suggested method, but wish to pre-populate(?) I do not know when an update will fix pre-population, I would very much like this as well to be honest…

If you provide some example code, @dadv92, I would like to give it a look if you wish? :slight_smile: My tabs in my app are working fine.

I also have this problem. I am using the most up to date version of Dash and I have each tab in its own separate Div. I did not have this problem until I updated my dash packages.

I removed the default tab part of my code everything went back to working normally. However with the default bit in it still has the above problem.

I wanted to include two codes snippets that generate two apps detailing the problems people have outlined above. The first app uses callbacks to generate tabs which works with no issues. The second app generates all the tabs in the app layout and when the callback is executed for the second tab, it takes the user back to the first tab.

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

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

tab1_layout = [
    html.Div([
        html.H2("This is the default first tab page.")
    ])
]

tab2_layout = [
    html.Div([
        dcc.RadioItems(
            id='dropdown',
            options=[{'label': i, 'value': i} for i in range(3)],
            value=0,
        ),
        html.H2(id='header')
    ])
]

app.layout = html.Div([
    dcc.Tabs(id="tabs", value=1, children=[
        dcc.Tab(label='1', value=1),
        dcc.Tab(label='2', value=2)
    ]),
    html.Div(id='tab-output')
])

@app.callback(
    Output('tab-output', 'children'),
    [Input('tabs', 'value')]
)
def show_content(value):
    if value == 1:
        return tab1_layout
    elif value == 2:
        return tab2_layout

@app.callback(
    Output('header', 'children'),
    [Input('dropdown', 'value')])
def update_header(v):
    return 'The selected number is ' + str(v)

if __name__ == "__main__":
    app.run_server(debug=True, host='0.0.0.0', port=7000)

This is the second app:

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

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

tab1_layout = [
    html.Div([
        html.H2("This is the default first tab page.")
    ])
]

tab2_layout = [
    html.Div([
        dcc.RadioItems(
            id='dropdown',
            options=[{'label': i, 'value': i} for i in range(3)],
            value=0,
        ),
        html.H2(id='header')
    ])
]

app.layout = html.Div([
    dcc.Tabs(id="tabs", value=1, children=[
        dcc.Tab(label='1', value=1, children=tab1_layout),
        dcc.Tab(label='2', value=2, children=tab2_layout)
    ])
])

@app.callback(
    Output('header', 'children'),
    [Input('dropdown', 'value')])
def update_header(v):
    return 'The selected number is ' + str(v)

if __name__ == "__main__":
    app.run_server(debug=True, host='0.0.0.0', port=7000)

For a few of my apps, I want to prepopulate so this is an issue when I have callbacks in other tabs. Right now, I’m using callbacks to generate tabs avoiding this problem.

Thanks for reporting everyone! We’re working on a fix in https://github.com/plotly/dash-core-components/issues/331