Skip to content

fixes aggressive remounting - #3934

Open
BSd3v wants to merge 3 commits into
plotly:devfrom
BSd3v:fix/aggressive-remount
Open

fixes aggressive remounting#3934
BSd3v wants to merge 3 commits into
plotly:devfrom
BSd3v:fix/aggressive-remount

Conversation

@BSd3v

@BSd3v BSd3v commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

fixes:

Issue was that on the first render of a component, it would trigger it to remount and clear the layoutHashes since it wasnt protected to see if it was the very first time that the component was rendering.

@sonarqubecloud

sonarqubecloud Bot commented Aug 7, 2026

Copy link
Copy Markdown

@KoolADE85 KoolADE85 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, such a sneaky issue!

@Mukller Mukller left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified the mechanism against DashWrapper.tsx on master: freshRenders.current += 1 lives in the useMemo(..., [_newRender]) block, so on first mount with _newRender set the counter reaches 1 before the useEffect runs — hence > 1 correctly suppresses the spurious resetComponentState dispatch that caused #3846/#3929. The core fix looks right.

Two edge cases worth considering, plus one request:

1. StrictMode / double-invoked renders. In development React StrictMode can invoke the render pass twice; if both invocations increment the ref before effects flush, freshRenders.current hits 2 on the very first mount and the remount bug resurfaces in dev builds. Does dash-renderer run wrappers under StrictMode anywhere? If yes, an escape hatch like comparing against a "seen first commit" boolean ref instead of counting renders might be more robust:

const didInitialRender = useRef(false);
useEffect(() => {
    if (_newRender && didInitialRender.current) {
        dispatch(resetComponentState({ itempath: componentPath }));
    }
    didInitialRender.current = true;
}, [...]);

2. Instance reuse across paths. freshRenders never resets when componentPath changes (renderedPath.current = componentPath updates, counter doesn't). If Dash ever recycles a wrapper instance for a different component, the new component's legitimate first _newRender will have counter > 1 and get an immediate state reset. Probably rare, but cheap to guard by resetting the counter (or using the boolean above) when renderedPath.current !== componentPath.

3. Tests. The diff is 1 line — could you add a renderer test reproducing the #3846 sequence (initial render must not dispatch resetComponentState) so it stays fixed?

Nice find either way — the root cause (reset firing on first render) matches both linked issues precisely.

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.

4 participants