Update components created via set_props (Bug?)

It seems to me that it is currently not possible to properly update components that were created dynamically using set_props.

I don’t have a problem updating a statically added component using either Output (Case 1 in the MRE) or set_props (Case 2 in the MRE).

I don’t have a problem to update the component via set_props when it was dynamically added to the layout using the standard Output of a callback (Case 3 in the MRE).

However, adding the component using set_props seems to miss registering the component in the tree/layout in such a way, that it can later be discovered by an another call to set_props (Case 4 in the MRE).

Is this supposed to be like this? Any ideas?

Dash version is v3.2.0, but I also tried v2.18.2

Error message is: ID running component not found in layout. Component defined in running keyword not found in layout. Component id: …

from dash import Dash, Input, Output, callback, html, set_props
from dash.exceptions import PreventUpdate

# using suppress_callback_exceptions=True would suppress the error in case 4, but the update won't work
app = Dash(suppress_callback_exceptions=False)

app.layout = html.Div(
        [
            html.Div(
                [
                    html.H2("Case 1 - Layout: static & Update: output"),
                    html.Div("The result element is created statically during initial layout. Update is done via an Output element of a callback."),
                    html.Button("Create", id="static-output-create-button"),
                    html.Button("Update", id="static-output-update-button", disabled=True),
                    html.Div(id="static-output-container", children=[
                        html.H3(id="static-output-result", children="Clicks: ?")
                    ])
                ],
            ),
            html.Div(
                [
                    html.H2("Case 2 - Layout: static & Update: set_props"),
                    html.Div("The result element is created statically during initial layout. Update is done via a call to set_props."),
                    html.Button("Create", id="static-setprops-create-button"),
                    html.Button("Update", id="static-setprops-update-button", disabled=True),
                    html.Div(id="static-setprops-container", children=[
                        html.H3(id="static-setprops-result", children="Clicks: ?")
                    ])
                ],
            ),
            html.Div(
                [
                    html.H2("Case 3 - Layout: dynamic via output & Update: set_props"),
                    html.Div("The result element is created dynamically via an Output element of a callback. Update is done via a call to set_props."),
                    html.Button("Create", id="output-setprops-create-button"),
                    html.Button("Update", id="output-setprops-update-button", disabled=True),
                    html.Div(id="output-setprops-container")
                ],
            ),
            html.Div(
                [
                    html.H2("Case 4 - Layout: dynamic via set_props & Update: set_props"),
                    html.Div("The result element is created dynamically via a call to set_props. Update is done via a call to set_props."),
                    html.Div("The update does not work in this case."),
                    html.Button("Create", id="setprops-setprops-create-button"),
                    html.Button("Update", id="setprops-setprops-update-button", disabled=True),
                    html.Div(id="setprops-setprops-container")
                ],
            ),
        ]
    )

# Case 1 - Layout: static & Update: output

@callback(
    Output("static-output-update-button", "disabled"),
    Output("static-output-result", "children"),
    Input("static-output-create-button", "n_clicks"),
)
def static_output_create_title(n_clicks: int) -> tuple[bool, str]:
    if n_clicks is None:
        raise PreventUpdate()
    return False, "Clicks: 0"

@callback(
    Output("static-output-result", "children", allow_duplicate=True),
    Input("static-output-update-button", "n_clicks"),
    prevent_initial_call=True,
)
def static_output_update_title(n_clicks: int) -> str:
    if n_clicks is None:
        raise PreventUpdate()
    return f"Clicks: {n_clicks}"

# Case 2 - Layout: static & Update: set_props

@callback(
    Output("static-setprops-update-button", "disabled"),
    Input("static-setprops-create-button", "n_clicks"),
)
def static_setprops_create_title(n_clicks: int) -> bool:
    if n_clicks is None:
        raise PreventUpdate()
    set_props(component_id="static-setprops-result", props={"children": "Clicks: 0"})
    return False

@callback(
    Input("static-setprops-update-button", "n_clicks"),
)
def static_setprops_update_title(n_clicks: int) -> None:
    if n_clicks is None:
        raise PreventUpdate()
    set_props(component_id="static-setprops-result", props={"children": f"Clicks: {n_clicks}"})

# Case 3 - Layout: dynamic via output & Update: set_props
    
@callback(
    Output("output-setprops-update-button", "disabled"),
    Output("output-setprops-container", "children"),
    Input("output-setprops-create-button", "n_clicks"),
)
def output_setprops_create_title(n_clicks: int) -> tuple[bool, html.H3]:
    if n_clicks is None:
        raise PreventUpdate()
    return False, html.H3(id="output-setprops-result", children="Clicks: 0")

@callback(
    Input("output-setprops-update-button", "n_clicks"),
)
def output_setprops_update_title(n_clicks: int) -> None:
    if n_clicks is None:
        raise PreventUpdate()
    set_props(component_id="output-setprops-result", props={"children": f"Clicks: {n_clicks}"})

# Case 4 - Layout: dynamic via set_props & Update: set_props

@callback(
    Output("setprops-setprops-update-button", "disabled"),
    Input("setprops-setprops-create-button", "n_clicks"),
)
def setprops_setprops_create_title(n_clicks: int) -> bool:
    if n_clicks is None:
        raise PreventUpdate()
    set_props("setprops-setprops-container", {"children": html.H3(id="setprops-setprops-result", children="Clicks: 0")})
    return False

@callback(
    Input("setprops-setprops-update-button", "n_clicks"),
)
def setprops_setprops_update_title(n_clicks: int) -> None:
    if n_clicks is None:
        raise PreventUpdate()
    set_props(component_id="setprops-setprops-result", props={"children": f"Clicks: {n_clicks}"})
    
if __name__ == "__main__":
    app.run(debug=True)

hi @datenzauberai
We are aware of this. Hopefully, this can be fixed in the next version (PR).
Thank you for reporting it.

2 Likes

Awesome! Thanks for the quick reply. I already had the suspicion that it might be connected to [BUG] Components added through set_props() cannot trigger related callback functions. · Issue #3316 · plotly/dash · GitHub , but as it was about triggering callback functions I thought it might possibly be a different issue.

I tried using the current dev branch and it works now indeed :+1:

1 Like