Conversation
A callback whose running= argument targets a pattern-matching id would throw "state.paths.objs[idKey] is undefined" whenever no component with that id shape was currently rendered - for example after navigating to a page in a multi-page app that does not contain those components. getAllPMCIds indexed paths.objs unconditionally, so an id shape that was never registered produced undefined and blew up on .map. It now returns an empty list, matching what resolveDeps and getPath already do for the same lookup. That alone was not enough: replacePMC used extras.length to decide whether a wildcard had been expanded, so an expansion that legitimately matched nothing fell through to returning [replaced] - an id containing only the non-wildcard keys. sideUpdate would then try to update a component with that malformed pattern id. replacePMC now tracks expansion explicitly, and sideUpdate skips pattern-matching outputs that resolve to no components.
770c783 to
79dc738
Compare
|
|
Thanks for the PR! Our team will take a look and provide some feedback. |
T4rk1n
left a comment
There was a problem hiding this comment.
Fix is right, but the fix isn't fully tested yet. The diagnosis is accurate: getAllPMCIds was the one caller of state.paths.objs[idKey] that didn't guard the lookup (resolveDeps and getPath both do), and the extras.length overload in replacePMC did fall through to a partial id on a zero match. The explicit expanded flag is the correct split between "a wildcard expanded" and "the expansion was empty," and all the side-update kinds (running, runningOff, progress, set_props) go through the same guard, so the empty match is handled symmetrically.
The gap is coverage. This is two changes and only one is tested. The new unit tests exercise getAllPMCIds/replacePMC, but the callbacks.ts guard, the half that actually stops the unresolved pattern from reaching updateComponent, has no test at all, and nothing here reproduces #3297 end to end. The reported failure is a navigate-then-click crash in a running app; the PR proves the helpers in isolation but never proves the crash is gone. Please add a renderer integration test next to test_cbwc008_running_match: a two-page app, navigate to a page that renders none of the wildcard components, click the button, then assert the browser console is clean and the callback's own output still updates. That fails before this change and passes after, and it covers the callbacks.ts early return, not just the helpers.
Also tighten the CHANGELOG entry to two lines (house style, detail goes in .ai/): the crash plus the fix, drop the parenthetical.
|
|
||
| const props = propName ? {[propName]: value} : value; | ||
|
|
||
| if (isPatternMatching && replacedIds.length === 0) { |
There was a problem hiding this comment.
This early return is the half of the fix that keeps the unresolved pattern ({type: 'loading'} with no id) out of updateComponent, and it has no test. The unit tests stop at replacePMC returning [] and never reach this branch. Add an integration test modeled on test_cbwc008_running_match that reproduces #3297 directly: multi-page app, navigate to a page with none of the ALL components, click, then assert the console is clean and the callback output updated. That exercises this line and proves the actual crash is gone.
| - [#3646](https://github.com/plotly/dash/pull/3646) Remove React 16 support (`16.14.0` is no longer an accepted value for `REACT_VERSION` / `_set_react_version`). | ||
|
|
||
| ### Fixed | ||
| - [#3957](https://github.com/plotly/dash/pull/3957) Fix a `running` argument using a pattern-matching `ALL`/`ALLSMALLER` id crashing the renderer with `state.paths.objs[idKey] is undefined` when none of the matching components are on the current page (for example after navigating to another page in a multi-page app). The wildcard now resolves to an empty set of components and the callback proceeds without any side updates. Fixes [#3297](https://github.com/plotly/dash/issues/3297). |
There was a problem hiding this comment.
Trim to two lines. Something like: "Fix running= with a pattern-matching ALL/ALLSMALLER id crashing the renderer when none of the matching components are on the current page. Fixes #3297." The navigation example and the resolve-to-empty detail belong in the commit body or .ai/.



Callbacks that use a pattern-matching id in
running=crash the renderer as soon as you're on a page where none of those components exist. From #3297:Click the button while you're on the home page and it works. Navigate to
/blank, which renders none of the{'type': 'loading', ...}components, click again, and the browser console throwsstate.paths.objs[idKey] is undefined. The callback never gets to run.What was going wrong
getAllPMCIdsindexesstate.paths.objs[idKey]and calls.mapon the result.paths.objsonly has a key for an id shape that is actually rendered somewhere, so on a page with none of those components the lookup returnsundefinedand.mapthrows. The two other places that do this same lookup —resolveDepsindependencies_ts.tsandgetPathinpaths.js— both guard it and return an empty result, so this was the odd one out. Guarding it the same way is the direct fix.That turned out not to be the whole story though.
replacePMCdecides whether a wildcard was expanded by checkingextras.length:An
ALLthat legitimately matches zero components expands to an empty list, which is falsy, so control falls through toreturn [replaced]— andreplacedonly ever received the non-wildcard keys, because the wildcard branch writes toextrasinstead. So once the crash is gone you get[{type: 'loading'}]back: an id with theidkey missing entirely.sideUpdatethen treats that as a real component id and tries to update it. So I tracked expansion with an explicit flag rather than inferring it from the length, and madesideUpdatedrop pattern-matching outputs that resolve to nothing, since in that casecomponentIdstill holds the unresolved pattern and isn't safe to use.The "matches nothing" case is the correct outcome here, not an error — there is simply nothing to show or hide, and the callback should proceed normally. That's also why this doesn't route through the existing "ID running component not found in layout" error: that one is for a concrete id that isn't in the layout, whereas a wildcard matching zero components is expected behaviour when you're on a different page.
Testing
Added
dash/dash-renderer/tests/patternMatching.test.js, which had no unit coverage before. It coversMATCH,ALL, plain concrete ids, and the two empty-page cases.Before the source change, with the tests in place:
That's the same error as the reported one, and the
MATCH/ALL/concrete-id cases passed throughout, which is what confines the bug to the empty-match path.After:
Lint is clean on the touched files (
eslint0 errors,prettier --list-differentempty).tsc --noEmitreports the same set of pre-existingnode_modulestype errors before and after — none from these files.Fixes #3297