Prevent_initial_callback ineffective

I am creating a new topic as this has not been resolved and other related threads grew to other topics. But as the title says, no matter if I set prevent_initial_call=True at callback level, the callback does get executed at startup and refresh. Here is a MWE: :

import json
import dash_html_components as html
import dash_core_components as dcc
from dash import Dash
from dash_extensions.enrich import Output, Input
import mwe_test_component
app = Dash(title='MWE')

server = app.server
app.config.suppress_callback_exceptions = True

def serve_layout():
    return html.Div([dcc.Store(id='cache'),

app.layout = serve_layout

@app.callback(Output('page-content', 'children'),
              [Input('url', 'pathname')])
def display_page(pathname):

@app.callback([Output("log", "children"), Output("cache", "data")],
              Input("button", "n_clicks"), prevent_initial_call=True)
def click(n_clicks):
    data_set = {"key1": [1, 2, 3], "key2": [4, 5, 6]}
    json_dump = json.dumps(data_set)
    return f"clicked ({n_clicks})", json_dump

if __name__ == '__main__':
    app.run_server(port=7777, dev_tools_hot_reload=True, debug=True)

import dash_html_components as html

def app():
    return html.Div([
        html.Button("button", id="button", n_clicks=0),
        html.Div("Initial value", id="log")])

Is this a bug with Dash or how the app is structured?

I guess that’s because your are using “dynamic” layout. Did you try with a “static” layout, i.e. assigning a rendered output instead of the layout function (I know this is probably not what you want and thus doesn’t solve your problem)?

Indeed I need this dynamic layout for my app to work when deployed, but even with a “static” layout, the callback still gets triggered at startup anyway.

I just tested. On dash==1.20.0 it works for both dynamic and static layouts. Here is my mwe (yours didn’t run, you were importing a local file),

import dash_html_components as html
from dash import Dash
from dash_extensions.enrich import Output, Input

def serve_layout():
    return html.Div([html.Div(id='trigger'), html.Div(id='page-content')])

app = Dash(prevent_initial_callbacks=True)
app.layout = serve_layout

@app.callback(Output('page-content', 'children'), [Input('trigger', 'children')])
def test(_):
    return "If you see this, prevent_initial_call didn't work"

if __name__ == '__main__':

Right, this one works for me too.
In my MWE, I pasted the file that is imported so you can run it.
I suspect that is the issue then, that importing the layout breaks things somehow?

No, it doesn’t matter how you structure the code. However, the prevent_initial_calls flag doesn’t prevent the callback from firering when you dynamically add the input to the page after it has been rendered. This seems indeed to be the case for your example now that I look a bit closer at your code.

Aha yes that could well be the case. The intention here is to have a multipage app, so the content is loaded depending on the url. I’ll look into options from the doc, if that is the intended behavior.

I believe that it is. That being said, I can see why you would prefer a different behavior for multipage apps. From that perspective, this might be a suitable feature to implement in dash-extensions after all, if we can come up with a concise way to determine if a callback should be fired or not.

I would have expected most multipage apps to require this feature but maybe there is something I don’t quite grasp, or the multipage apps are an uncommon use case of Dash?
In any case for the time being I will just assume that all callbacks are executed and insert some logic in each to control what they do at startup. But that feature would greatly clean things up.