diff --git a/CHANGELOG.md b/CHANGELOG.md index ec279e29fa..ec3fff29cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). - [#3465](https://github.com/plotly/dash/pull/3465) Plotly cloud integrations, add devtool API, placeholder plotly cloud CLI & publish button, `dash[cloud]` extra dependencies. ## Fixed +- [#3490](https://github.com/plotly/dash/pull/3490) Fix stack overflow when circular callbacks are displayed on the devtool callback - [#3395](https://github.com/plotly/dash/pull/3395) Fix Components added through set_props() cannot trigger related callback functions. Fix [#3316](https://github.com/plotly/dash/issues/3316) - [#3415](https://github.com/plotly/dash/pull/3415) Fix the error triggered when only a single no_update is returned for client-side callback functions with multiple Outputs. Fix [#3366](https://github.com/plotly/dash/issues/3366) - [#3416](https://github.com/plotly/dash/issues/3416) Fix DeprecationWarning in dash/_jupyter.py by migrating from deprecated ipykernel.comm.Comm to comm module diff --git a/dash/dash-renderer/src/components/error/CallbackGraph/CallbackGraphEffects.js b/dash/dash-renderer/src/components/error/CallbackGraph/CallbackGraphEffects.js index 1016650b44..f06359f3d1 100644 --- a/dash/dash-renderer/src/components/error/CallbackGraph/CallbackGraphEffects.js +++ b/dash/dash-renderer/src/components/error/CallbackGraph/CallbackGraphEffects.js @@ -27,8 +27,11 @@ function getEdgeTypes(node) { * @returns {function} - cleanup function, for useEffect hook */ export function updateSelectedNode(cy, id) { - function ascend(node, collection) { - // FIXME: Should we include State parents but non-recursively? + function ascend(node, collection, visited = new Set()) { + if (visited.has(node.id())) { + return; + } + visited.add(node.id()); const type = node.data().type === 'callback' ? 'input' : 'output'; const edges = getEdgeTypes(node)[type]; const parents = edges.sources(); @@ -37,10 +40,14 @@ export function updateSelectedNode(cy, id) { if (node.data().type === 'property') { collection.merge(node.ancestors()); } - parents.forEach(node => ascend(node, collection)); + parents.forEach(node => ascend(node, collection, visited)); } - function descend(node, collection) { + function descend(node, collection, visited = new Set()) { + if (visited.has(node.id())) { + return; + } + visited.add(node.id()); const type = node.data().type === 'callback' ? 'output' : 'input'; const edges = getEdgeTypes(node)[type]; const children = edges.targets(); @@ -49,7 +56,7 @@ export function updateSelectedNode(cy, id) { if (node.data().type === 'property') { collection.merge(node.ancestors()); } - children.forEach(node => descend(node, collection)); + children.forEach(node => descend(node, collection, visited)); } if (id) { @@ -63,8 +70,6 @@ export function updateSelectedNode(cy, id) { // all all ancestors and descendants that are connected via Inputs // or Outputs (but not State). - // WARNING: No cycle detection! - const subtree = cy.collection(); subtree.merge(node); ascend(node, subtree);