plotly / plotly/dash

[BUG] dcc.Patch() wipes persistence of sibling pattern-matching elements

Open
#3,937 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug P2 size: 1
Dominant language
Python
Stars
24.4k
Forks
2.3k
Avg merge
2d 7h
Merged PRs (30d)
13

Description

Sibling of: https://github.com/plotly/dash/issues/3681
Environment

dash                 4.4.0

When patching a new element to list of elements with pattern matched ids, persistence is wiped from all sibling IDs.

Minimal example:

from dash import ALL, Dash, Input, Output, Patch, dcc, html, clientside_callback, callback

app = Dash(__name__)

def make_input(index):
    return dcc.Input(
        id={"type": "inp", "index": index},
        value="initial",
        persistence=True,
        persistence_type="local",
    )


app.layout = html.Div(
    [
        html.Button("add", id="add"),
        html.Button("clear", id="clear"),
        html.Div([make_input(0), make_input(1)], id="container"),
        html.Div(id="display"),
        html.Pre(id="storage"),
        dcc.Interval(id="local_storage_poll", interval=250),
        dcc.Input("Hello, I don't wipe", id='non-pattern-matching-id', persistence=True, persistence_type='local')
    ]
)


@callback(
    Output("container", "children"),
    Input("add", "n_clicks"),
    prevent_initial_call=True,
)
def add_input(n):
    patch = Patch()
    patch.append(make_input(n + 1))
    return patch


@callback(Output("display", "children"), Input({"type": "inp", "index": ALL}, "value"))
def show(values):
    return "|".join(values)


# Debug info, show local storage to make it easier to noticed when it's wiped
clientside_callback(
    """
    function display_persisted_vals() {
        return Object.keys(window.localStorage)
            .filter(k => k.startsWith('_dash_persistence.'))
            .map(k => k + ' = ' + window.localStorage.getItem(k))
            .join('\\n') || '(nothing persisted)';
    }
    """,
    Output("storage", "children"),
    Input("local_storage_poll", "n_intervals"),
)

# Debug helper, clear local storage to do a clean test
# (helpful after the fix when the storage doesn't wipe constantly, lol)
clientside_callback(
    """
    function clear_persistence() {
        Object.keys(window.localStorage)
            .filter(k => k.startsWith('_dash_persistence.'))
            .forEach(k => window.localStorage.removeItem(k));
        window.location.reload();
        return window.dash_clientside.no_update;
    }
    """,
    Output("clear", "n_clicks"),
    Input("clear", "n_clicks"),
    prevent_initial_call=True,
)


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

Here is a run of the minimal example app

Persistence works without a patch.
When a patch is applied persistence is wiped from siblings, but not from unrelated elements.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by running the minimal Dash application in the issue with Dash 4.4.0, then trace how dcc.Patch() updates children with pattern-matching IDs and how persistence is restored. Done means appending a new sibling preserves local persistence for existing pattern-matched inputs while unrelated persistence remains unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
frontend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.