dash icon indicating copy to clipboard operation
dash copied to clipboard

"raise PreventUpdate" prevents any side channel property updates through "set_props"

Open JNeiger opened this issue 1 year ago • 0 comments

Thanks for maintaining this library, I really appreciate it!

Describe your context

dash                              2.17.0
dash-core-components              2.0.0
dash-extensions                   1.0.15
dash-html-components              2.0.0
dash-table                        5.0.0

Describe the bug

When set_props is used within a callback that later hits raise PreventUpdate, the properties set by set_props do not propagate back to the server when the callback ends. This is in direct contrast to no_update. Based on my understanding of the docs here, both raise PreventUpdate and return no_update (if applied to all outputs) should both have the same behavior in regards to updating outputs.

@callback(...)
def callback_prevent_update(...):
  """Does NOT set prop_id_here->is_open to True"""
  set_props("prop_id_here", {"is_open": True})
  raise PreventUpdate

@callback(...)
def callback_no_update(...):
  """Does set prop_id_here->is_open to True"""
  set_props("prop_id_here", {"is_open": True})
  return no_update

Expected behavior

I expected that raise PreventUpdate would have the exact same behavior as return no_update (when applied to all outputs) which is that the outputs of a callback do not update, but the set_props call still goes through.

If it's expected that these produce two different behaviors, then it might be worth updating the caveats here with this case as it's unexpected behavior compared to the standard Input/Output behavior in callbacks. I'm happy to help update the documentation if there's a code pointer on where to add it! ( I didn't see the docs folder on a quick initial look)

Screenshots In the chrome networking tab, the original callback in the example returns the following: image

When raise PreventUpdate is added, the example returns the following (Note the status code in the second image is now 204 instead of 200): image image

Minimal Reproducible Example This is taken directly from here, with the only change being the raise PreventUpdate line

Steps to reproduce

  1. Load the page
  2. Click on one of the rows
  3. A modal should have popped up, but does not due to the raise PreventUpdate
import dash_ag_grid as dag
from dash import Dash, Input, html, ctx, callback, set_props
from dash.exceptions import PreventUpdate
import dash_bootstrap_components as dbc

app = Dash(__name__, external_stylesheets=[dbc.themes.SPACELAB])

rowData = [
    {"make": "Toyota", "model": "Celica", "price": 35000},
    {"make": "Ford", "model": "Mondeo", "price": 32000},
    {"make": "Porsche", "model": "Boxster", "price": 72000},
]

app.layout = html.Div(
    [
        dag.AgGrid(
            id="setprops-row-selection-popup",
            rowData=rowData,
            columnDefs=[{"field": i} for i in ["make", "model", "price"]],
            columnSize="sizeToFit",
            dashGridOptions={"rowSelection": "single", "animateRows": False},
        ),
        dbc.Modal(
            [
                dbc.ModalHeader("More information about selected row"),
                dbc.ModalBody(id="setprops-row-selection-modal-content"),
                dbc.ModalFooter(dbc.Button("Close", id="setprops-row-selection-modal-close", className="ml-auto")),
            ],
            id="setprops-row-selection-modal",
        ),
    ]
)

@callback(
    Input("setprops-row-selection-popup", "selectedRows"),
    Input("setprops-row-selection-modal-close", "n_clicks"),
    prevent_initial_call=True,
)
def open_modal(selection, _):
    if ctx.triggered_id == "setprops-row-selection-modal-close":
        # Close the modal
        set_props("setprops-row-selection-modal", {'is_open': False})
    elif ctx.triggered_id == "setprops-row-selection-popup" and selection:
        # Open the modal and display the selected row content
        content_to_display = "You selected " + ", ".join(
            [
                f"{s['make']} (model {s['model']} and price {s['price']})"
                for s in selection
            ]
        )
        set_props("setprops-row-selection-modal", {'is_open': True})
        set_props("setprops-row-selection-modal-content", {'children': content_to_display})

    raise PreventUpdate

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

JNeiger avatar Aug 22 '24 23:08 JNeiger