Change of behaviour of no_update in Dash 3.0.0

Hi,

I am updating my Dash app from version 2.11.1 to 3.0.0 and encountered an unexpected behavior with no_update. When no_update is returned as the output for a dcc.Store, instead of preserving the existing data, it overwrites it with an object of type NoUpdate. As a result, subsequent callbacks referencing this dcc.Store receive an invalid value.

Attached a sample code where this behaviour can be replicated

import dash
from dash import Input, Output, State, callback_context, dcc, html, ctx, no_update
import dash_bootstrap_components as dbc
from dash_extensions.enrich import DashProxy, Output, Input, State, Serverside, html, dcc, ServersideOutputTransform

# Initialize Dash app
app = dash.Dash(__name__)
app = DashProxy(transforms=[ServersideOutputTransform()])

# Sample Dropdown Options (Replace with actual values)
query_dropdown_options = [
    {"label": "ID1", "value": "id1"},
    {"label": "ID2", "value": "id2"},
]

query_id_options = [
    {"label": "qwer1234", "value": "qwer1234"},
    {"label": "asdf5678", "value": "asdf5678"},
]


# Layout
app.layout = html.Div([
    dcc.Store(id="store_dropdown"),
    dbc.Row(
        [
            dbc.Col(
                [
                    dbc.Row(
                        [
                            dbc.Col(
                                [
                                    dcc.Dropdown(
                                        id="my-multi-dynamic-dropdown",
                                        placeholder="select",
                                        style={"font-size": 12, "border-radius": 0, "width": "50%"},
                                        options=query_id_options,
                                    ),
                                ],
                            ),
                            dbc.Col(
                                [
                                    dcc.Dropdown(
                                        id="my-multi-dynamic-dropdown_type",
                                        options=query_dropdown_options,
                                        value="id1",
                                        searchable=False,
                                        className="custom-dropdown",
                                        clearable=False,
                                        style={
                                            "font-size": 12,
                                            "border-radius": 0,
                                            "padding-right": 0,
                                            "width": "50%",
                                        },
                                    )
                                ],
                            ),
                        ]
                    )
                ],
                width=4,
                id="id_dropdown_col",
            ),
            dbc.Col(
                dbc.Button(
                    "Query Data",
                    id="query-button",
                    className="mt-auto",
                    size="sm",
                    n_clicks=0,
                ),
                id="analytics_query_button_col",
                style={"display": "block"},
            ),
            dbc.Col(
                dbc.Button(
                    "Check Data",
                    id="check-button",
                    className="mt-auto",
                    size="sm",
                    n_clicks=0,
                ),
                id="analytics_check_button_col",
                style={"display": "block"},
            ),
        ],
        style={"margin-top": "5px"},
    ),
    dbc.Row(
        dbc.Col(
            html.Div(id='output-data'),
            width=12
        )
    )
])


@app.callback(
    Output("store_dropdown", "data"),
    Output("output-data", "children", allow_duplicate=True),
    Input("query-button", "n_clicks"),
    State("my-multi-dynamic-dropdown", "value"),
    State("my-multi-dynamic-dropdown_type", "value"),
)

def query(n_clicks, id, dropdown_type):
    if n_clicks > 0:
        if id is None or dropdown_type is None:
            return Serverside(no_update), "Invalid dropdown value"
        
        else:
        
            return Serverside("hello"), "data_loaded"
    
    return Serverside(no_update), no_update

@app.callback(
    Output("output-data", "children", allow_duplicate=True),
    Input("check-button", "n_clicks"),
    State("store_dropdown", "data"),
)

def check(n_clicks, data):
    if data is not None:
        return len(data)


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

Dash Versions:

Error image:

Steps to replicate:

  • Select one of the options in the first dropdown
  • Click on query data which will print ā€˜data_loaded’ below check data btn
  • Click on check data which will print 5 below the check data btn
  • Then clear the first dropdown, click on the query data. which will print ā€˜Invalid dropdown value’
  • Then click on check data. The error pops up ā€˜TypeError: object of type ā€˜NoUpdate’ has no len()’
1 Like

Hi @ksuryateja24

It looks like it might be caused by dash-extensions. Do you know if it’s compatible with dash 3.0?

@Emil

Hi @AnnMarieW,

Not sure if dash-extensions supports dash 3.0. It’s not mentioned in the documentation

Thank you

Just found this of dash repo
NoUpdate is a Valid Prop by 2Ryan09 Ā· Pull Request #3127 Ā· plotly/dash

isn’t this similar issue to mine?

I’m not sure - it might be related. @Emil do you know the answer?

It was never the intention that no_update should be wrapped in ServerSide. This wrapping basically means ā€œsave this value on the serverā€, which seems to be what happens. Hence, I would consider your observation expected behavior. To fix the problem, simply remove the wrapping,

@app.callback(
    Output("store_dropdown", "data"),
    Output("output-data", "children", allow_duplicate=True),
    Input("query-button", "n_clicks"),
    State("my-multi-dynamic-dropdown", "value"),
    State("my-multi-dynamic-dropdown_type", "value"),
)
def query(n_clicks, id, dropdown_type):
    if n_clicks > 0:
        if id is None or dropdown_type is None:
            return no_update, "Invalid dropdown value"

        else:
            return Serverside("hello"), "data_loaded"

    return no_update, no_update

I tested the above code the with the Dash 3 compatible release of dash-extensions (2.0.0), and it seems to work as intended :slight_smile:

3 Likes