Skip to content

Only remount parent-passed components when their identity changed#3881

Open
T4rk1n wants to merge 3 commits into
devfrom
fix/remounts
Open

Only remount parent-passed components when their identity changed#3881
T4rk1n wants to merge 3 commits into
devfrom
fix/remounts

Conversation

@T4rk1n

@T4rk1n T4rk1n commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Since #3570, every component passed down from a parent whose children were updated by a callback got a bumped render key, forcing React to unmount and remount the entire subtree on every callback run - even when the returned children were the same components with only new prop values. This reset descendant state and made large subtree updates 3-4x slower (#3846).

The forced remount is now conditional: the render key is only bumped when the component identity (namespace, type, id) at that path actually changed. The same component reconciles in place as before 4.2.0, while a different component at the same path still remounts, and stale descendant layout hashes are still reset - keeping #3330 fixed.

The one reverted semantic from #3570 is that returning the same component (same type and id) from a callback no longer resets its internal state; return it with a different id to force a remount.

Fixes #3846

Since #3570, every component passed down from a parent whose children
were updated by a callback got a bumped render key, forcing React to
unmount and remount the entire subtree on every callback run - even
when the returned children were the same components with only new prop
values. This reset descendant state and made large subtree updates 3-4x
slower (#3846).

The forced remount is now conditional: the render key is only bumped
when the component identity (namespace, type, id) at that path actually
changed. The same component reconciles in place as before 4.2.0, while
a different component at the same path still remounts, and stale
descendant layout hashes are still reset - keeping #3330 fixed.

The one reverted semantic from #3570 is that returning the same
component (same type and id) from a callback no longer resets its
internal state; return it with a different id to force a remount.

Fixes #3846

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@BSd3v

BSd3v commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

@T4rk1n

I believe that this resets to the initial behaviour of 4.1, this may be better served with a dash update type? For example, if the dev wants to reset the component via a callback, they just tell the callback to do so, which this forces its way down the tree? The default could be to not force remount.

@AnnMarieW

Copy link
Copy Markdown
Collaborator

I took this for a spin and ran the example apps that PR 3570 fixed, and they both worked correctly.
I also ran the example given in issue 3846 and verified that the performance has been restored to the same as in v4.1.

This looks good to me! @BSd3v are there any other use-cases to test?

@BSd3v

BSd3v commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

It's really just reimplementing a previous workaround and expanding the workaround features. This expands the use of arbitrary ids and can muddy up the DOM tree when unnecessary.

The main thing is that the dev wants to be able to reset the children-type to an initial state, while this workaround obviously works (as it had prior in 4.1) I feel that a more robust definitive property makes more sense. Instead of placing in the documents that in order to remount you need to change the id that contains the information or change what the component is, rather than the ability to pass something to the Output of a callback and force it.

@AnnMarieW

Copy link
Copy Markdown
Collaborator

Here's an example reported on the forum, that worked with pr 3570, but does not work on this branch:
https://community.plotly.com/t/dropdowns-and-dynamic-options-in-dash-4-0-0/96416

@T4rk1n

T4rk1n commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

Here's an example reported on the forum, that worked with pr 3570, but does not work on this branch: https://community.plotly.com/t/dropdowns-and-dynamic-options-in-dash-4-0-0/96416

I implemented a fix for the component as props rendering, which seems to be the root cause of issues.

@BSd3v

BSd3v commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

I think the issue I have with this is that this just merges the component updates from the callback to the underlying components rather than resetting the children to their initially rendered state. There are several components that do this, AG Grid, dmc.Select to name a few. These components keep states within themselves that listen to updates.

I believe that a more robust and dev controlled way to handle would be keep the mechanism created here to handle normal cases, but allow for the dev to specifically reset a component to its initial state based upon kwargs passed to the output.

From an intuition perspective, it makes more sense for the component to reset from a callback the information that I am providing in a callback as the truth of what should be rendered, unless specifically patched.

Keeps in-place reconcile as the default and adds an opt-in wrapper for
callback returns that need a hard reset. remount() sets a private
top-level marker (emitted by to_plotly_json, not a prop) that the
renderer reads to bump the render key, unmounting the existing instance
and mounting a fresh one instead of reconciling in place. This resets a
stateful component's internal state without changing its id.
@T4rk1n

T4rk1n commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

Resetting all the components all the time ends up being too expensive for the most simple cases as reported in #3846.

I've added dash.remount as an explicit way to reset the component entirely from a callback. Just wrap it around a component:

from dash import remount

@callback(Output("box", "children"), Input("btn", "n_clicks"))
def cb(n):
    return remount(dmc.Select(id="s", value=v))   # forces a fresh mount → resets internal state

@sonarqubecloud

Copy link
Copy Markdown

@BSd3v

BSd3v commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

To force a remount on a component in the clientside it would be:
{namespace, type, props, _dashprivate_remount} ?

@T4rk1n

T4rk1n commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

To force a remount on a component in the clientside it would be: {namespace, type, props, _dashprivate_remount} ?

Yes I confirm that works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] 4.2.0: callback-rendered children unmount/remount instead of updating in place (regression, bisects to #3570)

3 participants