Loading Bar Changing Tabs

I have computationally expensive callbacks when I switch between tabs in my Dash app. The “content as callback” method is ideal for my use case. However, when a user switches tabs, I want to clear the current content and display a loading symbol so it is obvious to the user things are in progress.

I currently have my callback setup like:

@app.callback(
        [
            Output(ids.TABS_CONTENT, "children"),
            Output(ids.TABS_LOADING_OUTPUT, "children")
        ],
        Input(ids.TABS, "value"),
    )
    def _render_tab(tab):
        div = get_div_content()
        return div, tab

The behavior I see out of this is a loading symbol when I first load the page to get TABS_CONTENT. If I change tabs, it just waits until a new output is available for TABS_CONTENT and I never see the loading symbol again.

My goal is when the user changes tabs, TABS_CONTENT is cleared, then TABS_LOADING_OUTPUT showing the loading symbol, and finally TABS_CONTENT is populated with new information once the callback is finished. I don’t have errors to troubleshoot so I’m struggling to solve this problem, any help is greatly appreciated.

Is there a way to restructure my callback to do that?

Hi @trevbot welcome to the forums.

You might use a background callback for this.

An example:

from dash import Dash, DiskcacheManager, Input, Output, html, dcc, callback
import time
import diskcache        # Diskcache for non-production apps when developing locally

cache = diskcache.Cache("./cache")
background_callback_manager = DiskcacheManager(cache)

app = Dash(__name__, background_callback_manager=background_callback_manager)

app.layout = html.Div(
    [
        html.Button(id="btn", children="change"),
        html.Div(id='output', children=["Button not clicked"]),
        dcc.Loading(
            html.Div(
                id='while_loading',
                children=["just some initial text"],
                style={
                    'visibility': 'hidden',
                }
            )
        )
    ]
)


@callback(
    output=[
        Output("output", "children"),
        Output("while_loading", "children")
    ],
    inputs=Input("btn", "n_clicks"),
    background=True,
    running=[
        (Output("output", "children"), "", ""),
        (Output("while_loading", "style"), {}, {'visibility': 'hidden'}),
    ],
    prevent_initial_call=True
)
def update_clicks(n_clicks):
    sec = 3
    time.sleep(sec)
    return [f"your expensive calculation took {sec} seconds"], 'dummy_string'


if __name__ == "__main__":
    app.run(debug=True)