Trigger callback when a page loads in order to update all plots/inputs

I have a Dash app with some pages where every plot I have gets its data from a hidden div.

The problem I have is that every time I load a page the plots doesn’t update their content until I trigger it using some imput element (like a radio button). I found a way to ensure that when the page loads it also populates the element I have problems with. To do so I create a dummy hidden div an use it as an input but it doesn’t seem an elegant way to do that.

I think that the proper way to implement that should be an event that is triggered when the page is changed and the pass it to the callback. But after a lot of trial and research I haven’t been able to do it.

Example to show the problem

In order to make my problem more clear I used the code from the multipage tutorial (https://dash.plot.ly/urls) to have an easy example to show.

The files I used are:
– app.py
– index.py
– apps/app1.py
– apps/app2.py

with the following code:

app.py

import dash
import os

app = dash.Dash()
server = app.server
app.config.supress_callback_exceptions = True

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(list("ABC"), id="data", style={"display":"none"}),
    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)

apps/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'),
    dcc.Link('Go to App 2', href='/apps/app2'),
    dcc.Dropdown(id='app-1-dropdown'),
    html.Div(id='dummy_div'),
])

@app.callback(
    Output('app-1-dropdown', 'options'),
    [Input('data', 'children')])
def update_dropdown(options):
    return [{"label": x, "value": x} for x in options]

apps/app2.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 2'),
    dcc.Link('Go to App 1', href='/apps/app1'),
    dcc.Dropdown(id='app-2-dropdown'),
    html.Div(id='dummy_div'),
])

@app.callback(
    Output('app-2-dropdown', 'options'),
    [Input('data', 'children'),
     Input('dummy_div', 'children')]) #This is what ensures that the dropdown loads its options
def update_dropdown(options, aux):
    return [{"label": x, "value": x} for x in options]

In this example when you open App1 the dropdown it don’t have the options defined. But App2 it does have them due to the Input('dummy_div', 'children').

Any suggestions on how to implement that in a more elegant way?

Thanks in advance!

4 Likes

Thanks for the workaround, I also had this issue but mine was loading a graph with DataTables. I used your solution and it worked like a charm. It does look messy but it works. If I find a cleaner solution I will make sure to let you know.

Hi,

Assuming that your data Div doesn’t change after page load you do not need any callbacks in app1 and app2 to render your dropdowns. Instead you should make your layout in app1 and app2 into a function that gets passed the data by index.py like this:

Index.py

from dash.dependencies import Input, Output, State
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(list("ABC"), id="data", style={"display":"none"}),
    html.Div(id='page-content')
])


@app.callback(Output('page-content', 'children'),
             [Input('url', 'pathname')],
             [State('data', 'children')])
def display_page(pathname, data):
    if pathname == '/apps/app1':
         return app1.layout(data)
    elif pathname == '/apps/app2':
         return app2.layout(data)
    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
def layout(options):
    return html.Div([
        html.H3('App 1'),
        dcc.Link('Go to App 2', href='/apps/app2'),
        dcc.Dropdown(id='app-1-dropdown', options=[{"label": x, "value": x} for x in options]),
    ])

app2.py

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

from app import app

def layout(options):
    return html.Div([
        html.H3('App 2'),
        dcc.Link('Go to App 1', href='/apps/app1'),
        dcc.Dropdown(id='app-2-dropdown', options=[{"label": x, "value": x} for x in options]),
    ])

If your data Div does change via some other Callback (time interval or something) then as you originally had it is fine. Also if the data doesn’t change, why have it in a hidden Div at all? Why not have a file on the server and include it in the layout by opening/importing it?

3 Likes

I have have a similar issue I am using parameters passed in on the url. The callback seems to trigger before it has the href value. Is there a way to control and or determine when the callback will run?

@app.callback(dash.dependencies.Output(‘field-dropdown’,‘value’),
[dash.dependencies.Input(‘url’, ‘href’)])
def display_page(href):
# this is called every page load and every URL change
# you can update your components with this URL in here
findq1,findq2,findq3 = href.split("?")
findq1e,findq2e = findq2.split("=")
findq1q,findq2q = findq3.split("=")
t1 = getdata(findq2q,findq2e)
return findq2e

error on start up of page.

…ewappOct19.py", line 178, in display_page
findq1,findq2,findq3 = href.split("?")
AttributeError: ‘NoneType’ object has no attribute ‘split’

1 Like

ryanicky, you should probably ask a new question, but this is what I do:

from dash.exceptions import PreventUpdate

...


@app.callback(dash.dependencies.Output(‘field-dropdown’,‘value’),
              [dash.dependencies.Input(‘url’, ‘href’)])
def display_page(href):
    if href is None:
        raise PreventUpdate
    ...

Thanks for the suggestion. It worked well. Much appreciated.

Hello,
Im having the same issue that you did, trying to popoulate a few graphs from a datatable i had on the previous “page”, but unfortunately his solution hasnt worked for me.
Any chance you can tell me what you did?
Thank you

It has been a while now but maybe it could be useful to someone else. I had a similar problem in a multipage app but in my case data were calculated in a callback inside the page and then used to plot different plots. It looks like the plots appears only when they are triggered by a button or another input component so I resolved it adding an “invisible button” that is triggered by the callback that calculate the database. It is not so elegant but it works.
So in the app layout:

app.layout = html.Div([
                       # your layout
                         dcc.Button(id='invisible_button', style={'display':'none'}) 
                         html.Div(list("ABC"), id="data", style={"display":"none"})

Then the callbacks are something like:

@app.callback(
              [Output('data','children'),
               Output('invisible_button','n_clicks')],
             [
               #your inputs
             ],
             [State('invisible_button','n_clicks')]
def calculate_data(inputs,button_clicks):
         .
         .
         .
       return your_datas, button_clicks+1

@app.callback(
              [Outputs...]
              [Input('invisible_button','n_clicks')]
              [State('data','children')]
def your_function(..):
         .
         .
         .