This repository was archived by the owner on Aug 29, 2025. It is now read-only.

Change renderer setProps assignation logic - #126

Merged
Marc-Andre-Rivet merged 28 commits into
masterfrom
set-props-callback
Mar 11, 2019
Merged

Change renderer setProps assignation logic#126
Marc-Andre-Rivet merged 28 commits into
masterfrom
set-props-callback

Conversation

@Marc-Andre-Rivet

@Marc-Andre-RivetMarc-Andre-Rivet commented Feb 27, 2019

Copy link
Copy Markdown
Contributor

Fixes#40.
(Supersedes) Fixeshttps://github.com/plotly/dash-docs/issues/352.
(Supersedes) Fixeshttps://github.com/plotly/dash-docs/issues/435

Currently setProps is passed to the components only if a component property is used as an input or a state in a callback. This causes two problems:

  • components do not update as expected as the props inside the component itself are only updated if setProps exists:
    here we set the own-props


    here we decide if there's a setProps

  • the table is in its own special land as it loops back on itself if it doesn't have a setProps (uses its own internal state to update itself) -- which is fine until the following happens: a callback updates the table AND no callback requires a table prop as input / state -- the table now updates itself into its state and receives updates into its props -- for reasons not detailed here, the table merges states unto props, overriding props in the process -- the net result is that the props update from the callbacks are overwritten by the table's state as in updating a datatable with editable=True dash-table#386

(the table will still need its loopback with this fix for standalone purposes)

Additionally, the current renderer logic is very permissive as to which props will be sent. If a prop is used as input / state, each time a prop is updated, all updated props will be sent to Dash. For most components the overhead is a minor nuisance but for the table, updating data and sending it back to Dash, if not required, is a major overhead, possibly orders of magnitude larger than the useful data.

This PR proposes a solution to both problems:

  • always pass setProps to the component
  • on setProps: (1) always update the component itself -- no more stale updates that are impossible to understand for users -- this is seriously probably the most frequent question I answer with "create a bogus callback on your prop as a temporary solution", (2) filter out the props that are not asked for by Dash

Problems that arise from this seem to mostly be related to the way we test..

  • since we now update the components correctly all the time, the layout contains additional props, some of which are time sensitive (e.g. n_click_timestamp) -- I've adjusted the tests so as to be less dependent on the layout details
  • this needs to be used in tests from dcc, html, dash, table before going forward
  • check performance impact (see Chris' comment)

Performance: #126 (comment)

Companion PRs
plotly/dash-core-components#478
plotly/dash-html-components#99
https://github.com/plotly/dash-docs/pull/439
plotly/dash-component-boilerplate#65

Marc-André Rivet added 2 commits February 27, 2019 13:34
- always update self
- only update Dash if an updated prop is listened to
Comment threadsrc/components/core/NotifyObservers.react.js Outdated
}
return children;
function NotifyObserversComponent({ children, setProps }) {
return React.cloneElement(children, { setProps });

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Always pass the setProps function to the component. It will censure itself as needed.

dependency.inputs.find(input => input.id === ownProps.id && input.property === key) ||
dependency.state.find(state => state.id === ownProps.id && state.property === key)
)
)(keysIn(newProps));

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

From all the props updated, find the ones listened for

id: ownProps.id,
props: pick(watchedKeys)(newProps)
}));
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Only dispatch to Dash if at least one watched prop is updated

itempath: stateProps.paths[ownProps.id],
};
itempath: stateProps.paths[ownProps.id]
}));

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The component used to only be updated when it had a watched prop -- this makes no sense as it prevents components from updating themselves and have consistent behavior in the FE in all usage scenarios.

Comment threadtests/test_render.py Outdated
@alexcjohnson

Copy link
Copy Markdown
Collaborator

This looks like a good idea to me, but I'm not sure I'm aware of all the possible implications. @T4rk1n would you mind taking a look?

@Marc-Andre-RivetMarc-Andre-Rivet changed the title Change renderer setProps assignation logic[WIP] Change renderer setProps assignation logicFeb 27, 2019
@chriddyp

Copy link
Copy Markdown
Member

Only dispatch to Dash if at least one watched prop is updated

I think this implementation makes sense. I originally made "setProps" conditional for dcc.GraphhoverData - if someone was just displaying a graph but not listening to its updates, I didn't want to subscribe the event handler for the graph and have it cause the entire tree to re-render on every hover:
https://github.com/plotly/dash-core-components/blob/83a3ea07fd8af9ee3ca57c8da8ed32483e96f87c/src/components/Graph.react.js#L134-L141

In this PR, if you're only re-rendering the component if it is part of a callback, then there shouldn't be any adverse performance effects 👌


On another note, from a documentation point of view, we'll want to update our 'react for python devs' guide (https://dash.plot.ly/react-for-python-developers) as well as the boilerplate. Should mostly just be a lot of 🔪 though 😸

Marc-André Rivet added 3 commits March 1, 2019 13:32
# Conflicts:
#	src/components/core/NotifyObservers.react.js
- memoize + PureComponent TreeContainer
@Marc-Andre-Rivet

Marc-Andre-Rivet commented Mar 2, 2019

Copy link
Copy Markdown
ContributorAuthor

setProps being always present triggers a significant number of additional updates for many scenarios, also setProps and loading_states being evaluated on each render creates a new function and new object, forcing re-renders -- modified the PR with a very experimental attempt at limiting the re-renders at the source and simplifying the tree structure / logic -- I'll retag reviewers once I feel I've (1) addressed the performance concerns, (2) verified it works in known scenarios

Comment threadsrc/TreeContainer.js Outdated
isEqualArgs(lastArgs, args) ?
lastResult :
(lastArgs = args) && (lastResult = fn(...args));
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

memoize and equality code copied over from the table for experimenting

Comment threadsrc/TreeContainer.js Outdated
@Marc-Andre-Rivet

Copy link
Copy Markdown
ContributorAuthor

Simplified the changes -- since the renders are mostly driven through changes to immutable values in the store, PureComponents + memoization actually provides no benefit.

Prepended renderer centric props with _dashprivate_.

@Marc-Andre-RivetMarc-Andre-Rivet changed the title [WIP] Change renderer setProps assignation logicChange renderer setProps assignation logicMar 5, 2019
@Marc-Andre-Rivet

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson I think this is ready for another look.
@chriddyp Did you have a chance to try it out against an existing / larger app?

Comment threadsrc/TreeContainer.js
Comment threadsrc/TreeContainer.js Outdated
Comment threadsrc/TreeContainer.js Outdated

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm happy with this 💃
@chriddyp did you still want to try it on a big app?

@chriddyp

Copy link
Copy Markdown
Member

did you still want to try it on a big app?

I shared the source of a private large app with @Marc-Andre-Rivet to test things out with. I'm 💃 either way.

Also note that there are a few places in the documentation that we should update once this is released:

@Marc-Andre-Rivet

Marc-Andre-Rivet commented Mar 11, 2019

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson@chriddyp

Did some actual performance tests, mostly against the dash-docs. Got the demo app working partially but against the latest version of dash but there were still multiple issues, fixed at lot of them and got the code running but in a weird state -- and not sure how significant the results really are.

In all cases, opening a new browser window for each version, the runs are all in the same tab. Version ordering changes randomly. One warm up run prior to taking results.

In the docs' dcc page, simply reloading the page gave the following results in milliseconds for 5 reloads:
Dash 0.39: 6046, 6228, 6135
Modified: 4667, 4653,4635
The sample is small but ~25% difference on 15 runs is probably significant.

Table paging page, clicking prev/nex 10 times each, in milliseconds:
Dash 0.39: 3398, 3421, 3223
Modified: 637, 640, 604
5-to-1 difference... this might be partially due to no callback requiring data

Table editing, modifying 10 cells per run, in milliseconds:
Dash 0.39: 2647, 2418, 2439
Modified: 1059, 1109, 1113
2-to-1 difference

The Graph example w/ hover, 20 hovers, in milliseconds:
Dash 0.39: 833, 385, 512
Modified: 671, 750, 588
Nothing significant with this set -- the variability is high.. could go either way I guess -- could do more runs to make sure.

The Graph example w/ hover, 5 page loads, in milliseconds:
Dash 0.39: 1996, 2006, 2104
Modified: 2037, 2073, 2009
Nothing significant with this set

It seems that at worse this has no impact for certain components (e.g. Graph) and that at best, for certain interactions the performance impact can be very significant.

@chriddyp

Copy link
Copy Markdown
Member

Nice, those look encouraging. Another page I just thought of is https://dash.plot.ly/all. It's a "hidden" page that we used to generate a PDF version of the docs. It loads all of the pages in the docs at once (takes like 40 seconds to load), probably the most render intensive page that I can think of.

@Marc-Andre-Rivet

Marc-Andre-Rivet commented Mar 11, 2019

Copy link
Copy Markdown
ContributorAuthor

Dry run from a new tab of /all gave:

Dash 0.39: "Updating..." disappeared after ~ 229 seconds
Dev tools crashed when trying to load the performance analysis 😁

Modified: "Updating..." disappeared after 17.2, 17.4 and 18.1 seconds

@Marc-Andre-Rivet

Copy link
Copy Markdown
ContributorAuthor

2 step merge -- will remove the refs for the feature branches of dcc and html right after tests pass everywhere

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unregistered dcc input components are having their contents cleared after callback updates

3 participants

@Marc-Andre-Rivet@alexcjohnson@chriddyp
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
This repository was archived by the owner on Aug 29, 2025. It is now read-only.

Change renderer setProps assignation logic - #126

Merged
Marc-Andre-Rivet merged 28 commits into
masterfrom
set-props-callback
Mar 11, 2019
Merged

Change renderer setProps assignation logic#126
Marc-Andre-Rivet merged 28 commits into
masterfrom
set-props-callback

Conversation

@Marc-Andre-Rivet

@Marc-Andre-RivetMarc-Andre-Rivet commented Feb 27, 2019

Copy link
Copy Markdown
Contributor

Fixes#40.
(Supersedes) Fixeshttps://github.com/plotly/dash-docs/issues/352.
(Supersedes) Fixeshttps://github.com/plotly/dash-docs/issues/435

Currently setProps is passed to the components only if a component property is used as an input or a state in a callback. This causes two problems:

  • components do not update as expected as the props inside the component itself are only updated if setProps exists:
    here we set the own-props


    here we decide if there's a setProps

  • the table is in its own special land as it loops back on itself if it doesn't have a setProps (uses its own internal state to update itself) -- which is fine until the following happens: a callback updates the table AND no callback requires a table prop as input / state -- the table now updates itself into its state and receives updates into its props -- for reasons not detailed here, the table merges states unto props, overriding props in the process -- the net result is that the props update from the callbacks are overwritten by the table's state as in updating a datatable with editable=True dash-table#386

(the table will still need its loopback with this fix for standalone purposes)

Additionally, the current renderer logic is very permissive as to which props will be sent. If a prop is used as input / state, each time a prop is updated, all updated props will be sent to Dash. For most components the overhead is a minor nuisance but for the table, updating data and sending it back to Dash, if not required, is a major overhead, possibly orders of magnitude larger than the useful data.

This PR proposes a solution to both problems:

  • always pass setProps to the component
  • on setProps: (1) always update the component itself -- no more stale updates that are impossible to understand for users -- this is seriously probably the most frequent question I answer with "create a bogus callback on your prop as a temporary solution", (2) filter out the props that are not asked for by Dash

Problems that arise from this seem to mostly be related to the way we test..

  • since we now update the components correctly all the time, the layout contains additional props, some of which are time sensitive (e.g. n_click_timestamp) -- I've adjusted the tests so as to be less dependent on the layout details
  • this needs to be used in tests from dcc, html, dash, table before going forward
  • check performance impact (see Chris' comment)

Performance: #126 (comment)

Companion PRs
plotly/dash-core-components#478
plotly/dash-html-components#99
https://github.com/plotly/dash-docs/pull/439
plotly/dash-component-boilerplate#65

Marc-André Rivet added 2 commits February 27, 2019 13:34
- always update self
- only update Dash if an updated prop is listened to
Comment threadsrc/components/core/NotifyObservers.react.js Outdated
}
return children;
function NotifyObserversComponent({ children, setProps }) {
return React.cloneElement(children, { setProps });

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Always pass the setProps function to the component. It will censure itself as needed.

dependency.inputs.find(input => input.id === ownProps.id && input.property === key) ||
dependency.state.find(state => state.id === ownProps.id && state.property === key)
)
)(keysIn(newProps));

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

From all the props updated, find the ones listened for

id: ownProps.id,
props: pick(watchedKeys)(newProps)
}));
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Only dispatch to Dash if at least one watched prop is updated

itempath: stateProps.paths[ownProps.id],
};
itempath: stateProps.paths[ownProps.id]
}));

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The component used to only be updated when it had a watched prop -- this makes no sense as it prevents components from updating themselves and have consistent behavior in the FE in all usage scenarios.

Comment threadtests/test_render.py Outdated
@alexcjohnson

Copy link
Copy Markdown
Collaborator

This looks like a good idea to me, but I'm not sure I'm aware of all the possible implications. @T4rk1n would you mind taking a look?

@Marc-Andre-RivetMarc-Andre-Rivet changed the title Change renderer setProps assignation logic[WIP] Change renderer setProps assignation logicFeb 27, 2019
@chriddyp

Copy link
Copy Markdown
Member

Only dispatch to Dash if at least one watched prop is updated

I think this implementation makes sense. I originally made "setProps" conditional for dcc.GraphhoverData - if someone was just displaying a graph but not listening to its updates, I didn't want to subscribe the event handler for the graph and have it cause the entire tree to re-render on every hover:
https://github.com/plotly/dash-core-components/blob/83a3ea07fd8af9ee3ca57c8da8ed32483e96f87c/src/components/Graph.react.js#L134-L141

In this PR, if you're only re-rendering the component if it is part of a callback, then there shouldn't be any adverse performance effects 👌


On another note, from a documentation point of view, we'll want to update our 'react for python devs' guide (https://dash.plot.ly/react-for-python-developers) as well as the boilerplate. Should mostly just be a lot of 🔪 though 😸

Marc-André Rivet added 3 commits March 1, 2019 13:32
# Conflicts:
#	src/components/core/NotifyObservers.react.js
- memoize + PureComponent TreeContainer
@Marc-Andre-Rivet

Marc-Andre-Rivet commented Mar 2, 2019

Copy link
Copy Markdown
ContributorAuthor

setProps being always present triggers a significant number of additional updates for many scenarios, also setProps and loading_states being evaluated on each render creates a new function and new object, forcing re-renders -- modified the PR with a very experimental attempt at limiting the re-renders at the source and simplifying the tree structure / logic -- I'll retag reviewers once I feel I've (1) addressed the performance concerns, (2) verified it works in known scenarios

Comment threadsrc/TreeContainer.js Outdated
isEqualArgs(lastArgs, args) ?
lastResult :
(lastArgs = args) && (lastResult = fn(...args));
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

memoize and equality code copied over from the table for experimenting

Comment threadsrc/TreeContainer.js Outdated
@Marc-Andre-Rivet

Copy link
Copy Markdown
ContributorAuthor

Simplified the changes -- since the renders are mostly driven through changes to immutable values in the store, PureComponents + memoization actually provides no benefit.

Prepended renderer centric props with _dashprivate_.

@Marc-Andre-RivetMarc-Andre-Rivet changed the title [WIP] Change renderer setProps assignation logicChange renderer setProps assignation logicMar 5, 2019
@Marc-Andre-Rivet

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson I think this is ready for another look.
@chriddyp Did you have a chance to try it out against an existing / larger app?

Comment threadsrc/TreeContainer.js
Comment threadsrc/TreeContainer.js Outdated
Comment threadsrc/TreeContainer.js Outdated

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm happy with this 💃
@chriddyp did you still want to try it on a big app?

@chriddyp

Copy link
Copy Markdown
Member

did you still want to try it on a big app?

I shared the source of a private large app with @Marc-Andre-Rivet to test things out with. I'm 💃 either way.

Also note that there are a few places in the documentation that we should update once this is released:

@Marc-Andre-Rivet

Marc-Andre-Rivet commented Mar 11, 2019

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson@chriddyp

Did some actual performance tests, mostly against the dash-docs. Got the demo app working partially but against the latest version of dash but there were still multiple issues, fixed at lot of them and got the code running but in a weird state -- and not sure how significant the results really are.

In all cases, opening a new browser window for each version, the runs are all in the same tab. Version ordering changes randomly. One warm up run prior to taking results.

In the docs' dcc page, simply reloading the page gave the following results in milliseconds for 5 reloads:
Dash 0.39: 6046, 6228, 6135
Modified: 4667, 4653,4635
The sample is small but ~25% difference on 15 runs is probably significant.

Table paging page, clicking prev/nex 10 times each, in milliseconds:
Dash 0.39: 3398, 3421, 3223
Modified: 637, 640, 604
5-to-1 difference... this might be partially due to no callback requiring data

Table editing, modifying 10 cells per run, in milliseconds:
Dash 0.39: 2647, 2418, 2439
Modified: 1059, 1109, 1113
2-to-1 difference

The Graph example w/ hover, 20 hovers, in milliseconds:
Dash 0.39: 833, 385, 512
Modified: 671, 750, 588
Nothing significant with this set -- the variability is high.. could go either way I guess -- could do more runs to make sure.

The Graph example w/ hover, 5 page loads, in milliseconds:
Dash 0.39: 1996, 2006, 2104
Modified: 2037, 2073, 2009
Nothing significant with this set

It seems that at worse this has no impact for certain components (e.g. Graph) and that at best, for certain interactions the performance impact can be very significant.

@chriddyp

Copy link
Copy Markdown
Member

Nice, those look encouraging. Another page I just thought of is https://dash.plot.ly/all. It's a "hidden" page that we used to generate a PDF version of the docs. It loads all of the pages in the docs at once (takes like 40 seconds to load), probably the most render intensive page that I can think of.

@Marc-Andre-Rivet

Marc-Andre-Rivet commented Mar 11, 2019

Copy link
Copy Markdown
ContributorAuthor

Dry run from a new tab of /all gave:

Dash 0.39: "Updating..." disappeared after ~ 229 seconds
Dev tools crashed when trying to load the performance analysis 😁

Modified: "Updating..." disappeared after 17.2, 17.4 and 18.1 seconds

@Marc-Andre-Rivet

Copy link
Copy Markdown
ContributorAuthor

2 step merge -- will remove the refs for the feature branches of dcc and html right after tests pass everywhere

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unregistered dcc input components are having their contents cleared after callback updates

3 participants

@Marc-Andre-Rivet@alexcjohnson@chriddyp
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Aug 29, 2025. It is now read-only.

Change renderer setProps assignation logic - #126

Merged
Marc-Andre-Rivet merged 28 commits into
masterfrom
set-props-callback
Mar 11, 2019
Merged

Change renderer setProps assignation logic#126
Marc-Andre-Rivet merged 28 commits into
masterfrom
set-props-callback

Conversation

@Marc-Andre-Rivet

@Marc-Andre-RivetMarc-Andre-Rivet commented Feb 27, 2019

Copy link
Copy Markdown
Contributor

Fixes#40.
(Supersedes) Fixeshttps://github.com/plotly/dash-docs/issues/352.
(Supersedes) Fixeshttps://github.com/plotly/dash-docs/issues/435

Currently setProps is passed to the components only if a component property is used as an input or a state in a callback. This causes two problems:

  • components do not update as expected as the props inside the component itself are only updated if setProps exists:
    here we set the own-props


    here we decide if there's a setProps

  • the table is in its own special land as it loops back on itself if it doesn't have a setProps (uses its own internal state to update itself) -- which is fine until the following happens: a callback updates the table AND no callback requires a table prop as input / state -- the table now updates itself into its state and receives updates into its props -- for reasons not detailed here, the table merges states unto props, overriding props in the process -- the net result is that the props update from the callbacks are overwritten by the table's state as in updating a datatable with editable=True dash-table#386

(the table will still need its loopback with this fix for standalone purposes)

Additionally, the current renderer logic is very permissive as to which props will be sent. If a prop is used as input / state, each time a prop is updated, all updated props will be sent to Dash. For most components the overhead is a minor nuisance but for the table, updating data and sending it back to Dash, if not required, is a major overhead, possibly orders of magnitude larger than the useful data.

This PR proposes a solution to both problems:

  • always pass setProps to the component
  • on setProps: (1) always update the component itself -- no more stale updates that are impossible to understand for users -- this is seriously probably the most frequent question I answer with "create a bogus callback on your prop as a temporary solution", (2) filter out the props that are not asked for by Dash

Problems that arise from this seem to mostly be related to the way we test..

  • since we now update the components correctly all the time, the layout contains additional props, some of which are time sensitive (e.g. n_click_timestamp) -- I've adjusted the tests so as to be less dependent on the layout details
  • this needs to be used in tests from dcc, html, dash, table before going forward
  • check performance impact (see Chris' comment)

Performance: #126 (comment)

Companion PRs
plotly/dash-core-components#478
plotly/dash-html-components#99
https://github.com/plotly/dash-docs/pull/439
plotly/dash-component-boilerplate#65

Marc-André Rivet added 2 commits February 27, 2019 13:34
- always update self
- only update Dash if an updated prop is listened to
Comment threadsrc/components/core/NotifyObservers.react.js Outdated
}
return children;
function NotifyObserversComponent({ children, setProps }) {
return React.cloneElement(children, { setProps });

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Always pass the setProps function to the component. It will censure itself as needed.

dependency.inputs.find(input => input.id === ownProps.id && input.property === key) ||
dependency.state.find(state => state.id === ownProps.id && state.property === key)
)
)(keysIn(newProps));

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

From all the props updated, find the ones listened for

id: ownProps.id,
props: pick(watchedKeys)(newProps)
}));
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Only dispatch to Dash if at least one watched prop is updated

itempath: stateProps.paths[ownProps.id],
};
itempath: stateProps.paths[ownProps.id]
}));

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The component used to only be updated when it had a watched prop -- this makes no sense as it prevents components from updating themselves and have consistent behavior in the FE in all usage scenarios.

Comment threadtests/test_render.py Outdated
@alexcjohnson

Copy link
Copy Markdown
Collaborator

This looks like a good idea to me, but I'm not sure I'm aware of all the possible implications. @T4rk1n would you mind taking a look?

@Marc-Andre-RivetMarc-Andre-Rivet changed the title Change renderer setProps assignation logic[WIP] Change renderer setProps assignation logicFeb 27, 2019
@chriddyp

Copy link
Copy Markdown
Member

Only dispatch to Dash if at least one watched prop is updated

I think this implementation makes sense. I originally made "setProps" conditional for dcc.GraphhoverData - if someone was just displaying a graph but not listening to its updates, I didn't want to subscribe the event handler for the graph and have it cause the entire tree to re-render on every hover:
https://github.com/plotly/dash-core-components/blob/83a3ea07fd8af9ee3ca57c8da8ed32483e96f87c/src/components/Graph.react.js#L134-L141

In this PR, if you're only re-rendering the component if it is part of a callback, then there shouldn't be any adverse performance effects 👌


On another note, from a documentation point of view, we'll want to update our 'react for python devs' guide (https://dash.plot.ly/react-for-python-developers) as well as the boilerplate. Should mostly just be a lot of 🔪 though 😸

Marc-André Rivet added 3 commits March 1, 2019 13:32
# Conflicts:
#	src/components/core/NotifyObservers.react.js
- memoize + PureComponent TreeContainer
@Marc-Andre-Rivet

Marc-Andre-Rivet commented Mar 2, 2019

Copy link
Copy Markdown
ContributorAuthor

setProps being always present triggers a significant number of additional updates for many scenarios, also setProps and loading_states being evaluated on each render creates a new function and new object, forcing re-renders -- modified the PR with a very experimental attempt at limiting the re-renders at the source and simplifying the tree structure / logic -- I'll retag reviewers once I feel I've (1) addressed the performance concerns, (2) verified it works in known scenarios

Comment threadsrc/TreeContainer.js Outdated
isEqualArgs(lastArgs, args) ?
lastResult :
(lastArgs = args) && (lastResult = fn(...args));
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

memoize and equality code copied over from the table for experimenting

Comment threadsrc/TreeContainer.js Outdated
@Marc-Andre-Rivet

Copy link
Copy Markdown
ContributorAuthor

Simplified the changes -- since the renders are mostly driven through changes to immutable values in the store, PureComponents + memoization actually provides no benefit.

Prepended renderer centric props with _dashprivate_.

@Marc-Andre-RivetMarc-Andre-Rivet changed the title [WIP] Change renderer setProps assignation logicChange renderer setProps assignation logicMar 5, 2019
@Marc-Andre-Rivet

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson I think this is ready for another look.
@chriddyp Did you have a chance to try it out against an existing / larger app?

Comment threadsrc/TreeContainer.js
Comment threadsrc/TreeContainer.js Outdated
Comment threadsrc/TreeContainer.js Outdated

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm happy with this 💃
@chriddyp did you still want to try it on a big app?

@chriddyp

Copy link
Copy Markdown
Member

did you still want to try it on a big app?

I shared the source of a private large app with @Marc-Andre-Rivet to test things out with. I'm 💃 either way.

Also note that there are a few places in the documentation that we should update once this is released:

@Marc-Andre-Rivet

Marc-Andre-Rivet commented Mar 11, 2019

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson@chriddyp

Did some actual performance tests, mostly against the dash-docs. Got the demo app working partially but against the latest version of dash but there were still multiple issues, fixed at lot of them and got the code running but in a weird state -- and not sure how significant the results really are.

In all cases, opening a new browser window for each version, the runs are all in the same tab. Version ordering changes randomly. One warm up run prior to taking results.

In the docs' dcc page, simply reloading the page gave the following results in milliseconds for 5 reloads:
Dash 0.39: 6046, 6228, 6135
Modified: 4667, 4653,4635
The sample is small but ~25% difference on 15 runs is probably significant.

Table paging page, clicking prev/nex 10 times each, in milliseconds:
Dash 0.39: 3398, 3421, 3223
Modified: 637, 640, 604
5-to-1 difference... this might be partially due to no callback requiring data

Table editing, modifying 10 cells per run, in milliseconds:
Dash 0.39: 2647, 2418, 2439
Modified: 1059, 1109, 1113
2-to-1 difference

The Graph example w/ hover, 20 hovers, in milliseconds:
Dash 0.39: 833, 385, 512
Modified: 671, 750, 588
Nothing significant with this set -- the variability is high.. could go either way I guess -- could do more runs to make sure.

The Graph example w/ hover, 5 page loads, in milliseconds:
Dash 0.39: 1996, 2006, 2104
Modified: 2037, 2073, 2009
Nothing significant with this set

It seems that at worse this has no impact for certain components (e.g. Graph) and that at best, for certain interactions the performance impact can be very significant.

@chriddyp

Copy link
Copy Markdown
Member

Nice, those look encouraging. Another page I just thought of is https://dash.plot.ly/all. It's a "hidden" page that we used to generate a PDF version of the docs. It loads all of the pages in the docs at once (takes like 40 seconds to load), probably the most render intensive page that I can think of.

@Marc-Andre-Rivet

Marc-Andre-Rivet commented Mar 11, 2019

Copy link
Copy Markdown
ContributorAuthor

Dry run from a new tab of /all gave:

Dash 0.39: "Updating..." disappeared after ~ 229 seconds
Dev tools crashed when trying to load the performance analysis 😁

Modified: "Updating..." disappeared after 17.2, 17.4 and 18.1 seconds

@Marc-Andre-Rivet

Copy link
Copy Markdown
ContributorAuthor

2 step merge -- will remove the refs for the feature branches of dcc and html right after tests pass everywhere

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unregistered dcc input components are having their contents cleared after callback updates

3 participants

@Marc-Andre-Rivet@alexcjohnson@chriddyp
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Aug 29, 2025. It is now read-only.

Change renderer setProps assignation logic - #126

Merged
Marc-Andre-Rivet merged 28 commits into
masterfrom
set-props-callback
Mar 11, 2019
Merged

Change renderer setProps assignation logic#126
Marc-Andre-Rivet merged 28 commits into
masterfrom
set-props-callback

Conversation

@Marc-Andre-Rivet

@Marc-Andre-RivetMarc-Andre-Rivet commented Feb 27, 2019

Copy link
Copy Markdown
Contributor

Fixes#40.
(Supersedes) Fixeshttps://github.com/plotly/dash-docs/issues/352.
(Supersedes) Fixeshttps://github.com/plotly/dash-docs/issues/435

Currently setProps is passed to the components only if a component property is used as an input or a state in a callback. This causes two problems:

  • components do not update as expected as the props inside the component itself are only updated if setProps exists:
    here we set the own-props


    here we decide if there's a setProps

  • the table is in its own special land as it loops back on itself if it doesn't have a setProps (uses its own internal state to update itself) -- which is fine until the following happens: a callback updates the table AND no callback requires a table prop as input / state -- the table now updates itself into its state and receives updates into its props -- for reasons not detailed here, the table merges states unto props, overriding props in the process -- the net result is that the props update from the callbacks are overwritten by the table's state as in updating a datatable with editable=True dash-table#386

(the table will still need its loopback with this fix for standalone purposes)

Additionally, the current renderer logic is very permissive as to which props will be sent. If a prop is used as input / state, each time a prop is updated, all updated props will be sent to Dash. For most components the overhead is a minor nuisance but for the table, updating data and sending it back to Dash, if not required, is a major overhead, possibly orders of magnitude larger than the useful data.

This PR proposes a solution to both problems:

  • always pass setProps to the component
  • on setProps: (1) always update the component itself -- no more stale updates that are impossible to understand for users -- this is seriously probably the most frequent question I answer with "create a bogus callback on your prop as a temporary solution", (2) filter out the props that are not asked for by Dash

Problems that arise from this seem to mostly be related to the way we test..

  • since we now update the components correctly all the time, the layout contains additional props, some of which are time sensitive (e.g. n_click_timestamp) -- I've adjusted the tests so as to be less dependent on the layout details
  • this needs to be used in tests from dcc, html, dash, table before going forward
  • check performance impact (see Chris' comment)

Performance: #126 (comment)

Companion PRs
plotly/dash-core-components#478
plotly/dash-html-components#99
https://github.com/plotly/dash-docs/pull/439
plotly/dash-component-boilerplate#65

Marc-André Rivet added 2 commits February 27, 2019 13:34
- always update self
- only update Dash if an updated prop is listened to
Comment threadsrc/components/core/NotifyObservers.react.js Outdated
}
return children;
function NotifyObserversComponent({ children, setProps }) {
return React.cloneElement(children, { setProps });

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Always pass the setProps function to the component. It will censure itself as needed.

dependency.inputs.find(input => input.id === ownProps.id && input.property === key) ||
dependency.state.find(state => state.id === ownProps.id && state.property === key)
)
)(keysIn(newProps));

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

From all the props updated, find the ones listened for

id: ownProps.id,
props: pick(watchedKeys)(newProps)
}));
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Only dispatch to Dash if at least one watched prop is updated

itempath: stateProps.paths[ownProps.id],
};
itempath: stateProps.paths[ownProps.id]
}));

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The component used to only be updated when it had a watched prop -- this makes no sense as it prevents components from updating themselves and have consistent behavior in the FE in all usage scenarios.

Comment threadtests/test_render.py Outdated
@alexcjohnson

Copy link
Copy Markdown
Collaborator

This looks like a good idea to me, but I'm not sure I'm aware of all the possible implications. @T4rk1n would you mind taking a look?

@Marc-Andre-RivetMarc-Andre-Rivet changed the title Change renderer setProps assignation logic[WIP] Change renderer setProps assignation logicFeb 27, 2019
@chriddyp

Copy link
Copy Markdown
Member

Only dispatch to Dash if at least one watched prop is updated

I think this implementation makes sense. I originally made "setProps" conditional for dcc.GraphhoverData - if someone was just displaying a graph but not listening to its updates, I didn't want to subscribe the event handler for the graph and have it cause the entire tree to re-render on every hover:
https://github.com/plotly/dash-core-components/blob/83a3ea07fd8af9ee3ca57c8da8ed32483e96f87c/src/components/Graph.react.js#L134-L141

In this PR, if you're only re-rendering the component if it is part of a callback, then there shouldn't be any adverse performance effects 👌


On another note, from a documentation point of view, we'll want to update our 'react for python devs' guide (https://dash.plot.ly/react-for-python-developers) as well as the boilerplate. Should mostly just be a lot of 🔪 though 😸

Marc-André Rivet added 3 commits March 1, 2019 13:32
# Conflicts:
#	src/components/core/NotifyObservers.react.js
- memoize + PureComponent TreeContainer
@Marc-Andre-Rivet

Marc-Andre-Rivet commented Mar 2, 2019

Copy link
Copy Markdown
ContributorAuthor

setProps being always present triggers a significant number of additional updates for many scenarios, also setProps and loading_states being evaluated on each render creates a new function and new object, forcing re-renders -- modified the PR with a very experimental attempt at limiting the re-renders at the source and simplifying the tree structure / logic -- I'll retag reviewers once I feel I've (1) addressed the performance concerns, (2) verified it works in known scenarios

Comment threadsrc/TreeContainer.js Outdated
isEqualArgs(lastArgs, args) ?
lastResult :
(lastArgs = args) && (lastResult = fn(...args));
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

memoize and equality code copied over from the table for experimenting

Comment threadsrc/TreeContainer.js Outdated
@Marc-Andre-Rivet

Copy link
Copy Markdown
ContributorAuthor

Simplified the changes -- since the renders are mostly driven through changes to immutable values in the store, PureComponents + memoization actually provides no benefit.

Prepended renderer centric props with _dashprivate_.

@Marc-Andre-RivetMarc-Andre-Rivet changed the title [WIP] Change renderer setProps assignation logicChange renderer setProps assignation logicMar 5, 2019
@Marc-Andre-Rivet

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson I think this is ready for another look.
@chriddyp Did you have a chance to try it out against an existing / larger app?

Comment threadsrc/TreeContainer.js
Comment threadsrc/TreeContainer.js Outdated
Comment threadsrc/TreeContainer.js Outdated

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm happy with this 💃
@chriddyp did you still want to try it on a big app?

@chriddyp

Copy link
Copy Markdown
Member

did you still want to try it on a big app?

I shared the source of a private large app with @Marc-Andre-Rivet to test things out with. I'm 💃 either way.

Also note that there are a few places in the documentation that we should update once this is released:

@Marc-Andre-Rivet

Marc-Andre-Rivet commented Mar 11, 2019

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson@chriddyp

Did some actual performance tests, mostly against the dash-docs. Got the demo app working partially but against the latest version of dash but there were still multiple issues, fixed at lot of them and got the code running but in a weird state -- and not sure how significant the results really are.

In all cases, opening a new browser window for each version, the runs are all in the same tab. Version ordering changes randomly. One warm up run prior to taking results.

In the docs' dcc page, simply reloading the page gave the following results in milliseconds for 5 reloads:
Dash 0.39: 6046, 6228, 6135
Modified: 4667, 4653,4635
The sample is small but ~25% difference on 15 runs is probably significant.

Table paging page, clicking prev/nex 10 times each, in milliseconds:
Dash 0.39: 3398, 3421, 3223
Modified: 637, 640, 604
5-to-1 difference... this might be partially due to no callback requiring data

Table editing, modifying 10 cells per run, in milliseconds:
Dash 0.39: 2647, 2418, 2439
Modified: 1059, 1109, 1113
2-to-1 difference

The Graph example w/ hover, 20 hovers, in milliseconds:
Dash 0.39: 833, 385, 512
Modified: 671, 750, 588
Nothing significant with this set -- the variability is high.. could go either way I guess -- could do more runs to make sure.

The Graph example w/ hover, 5 page loads, in milliseconds:
Dash 0.39: 1996, 2006, 2104
Modified: 2037, 2073, 2009
Nothing significant with this set

It seems that at worse this has no impact for certain components (e.g. Graph) and that at best, for certain interactions the performance impact can be very significant.

@chriddyp

Copy link
Copy Markdown
Member

Nice, those look encouraging. Another page I just thought of is https://dash.plot.ly/all. It's a "hidden" page that we used to generate a PDF version of the docs. It loads all of the pages in the docs at once (takes like 40 seconds to load), probably the most render intensive page that I can think of.

@Marc-Andre-Rivet

Marc-Andre-Rivet commented Mar 11, 2019

Copy link
Copy Markdown
ContributorAuthor

Dry run from a new tab of /all gave:

Dash 0.39: "Updating..." disappeared after ~ 229 seconds
Dev tools crashed when trying to load the performance analysis 😁

Modified: "Updating..." disappeared after 17.2, 17.4 and 18.1 seconds

@Marc-Andre-Rivet

Copy link
Copy Markdown
ContributorAuthor

2 step merge -- will remove the refs for the feature branches of dcc and html right after tests pass everywhere

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unregistered dcc input components are having their contents cleared after callback updates

3 participants

@Marc-Andre-Rivet@alexcjohnson@chriddyp
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
This repository was archived by the owner on Aug 29, 2025. It is now read-only.

Change renderer setProps assignation logic - #126

Merged
Marc-Andre-Rivet merged 28 commits into
masterfrom
set-props-callback
Mar 11, 2019
Merged

Change renderer setProps assignation logic#126
Marc-Andre-Rivet merged 28 commits into
masterfrom
set-props-callback

Conversation

@Marc-Andre-Rivet

@Marc-Andre-RivetMarc-Andre-Rivet commented Feb 27, 2019

Copy link
Copy Markdown
Contributor

Fixes#40.
(Supersedes) Fixeshttps://github.com/plotly/dash-docs/issues/352.
(Supersedes) Fixeshttps://github.com/plotly/dash-docs/issues/435

Currently setProps is passed to the components only if a component property is used as an input or a state in a callback. This causes two problems:

  • components do not update as expected as the props inside the component itself are only updated if setProps exists:
    here we set the own-props


    here we decide if there's a setProps

  • the table is in its own special land as it loops back on itself if it doesn't have a setProps (uses its own internal state to update itself) -- which is fine until the following happens: a callback updates the table AND no callback requires a table prop as input / state -- the table now updates itself into its state and receives updates into its props -- for reasons not detailed here, the table merges states unto props, overriding props in the process -- the net result is that the props update from the callbacks are overwritten by the table's state as in updating a datatable with editable=True dash-table#386

(the table will still need its loopback with this fix for standalone purposes)

Additionally, the current renderer logic is very permissive as to which props will be sent. If a prop is used as input / state, each time a prop is updated, all updated props will be sent to Dash. For most components the overhead is a minor nuisance but for the table, updating data and sending it back to Dash, if not required, is a major overhead, possibly orders of magnitude larger than the useful data.

This PR proposes a solution to both problems:

  • always pass setProps to the component
  • on setProps: (1) always update the component itself -- no more stale updates that are impossible to understand for users -- this is seriously probably the most frequent question I answer with "create a bogus callback on your prop as a temporary solution", (2) filter out the props that are not asked for by Dash

Problems that arise from this seem to mostly be related to the way we test..

  • since we now update the components correctly all the time, the layout contains additional props, some of which are time sensitive (e.g. n_click_timestamp) -- I've adjusted the tests so as to be less dependent on the layout details
  • this needs to be used in tests from dcc, html, dash, table before going forward
  • check performance impact (see Chris' comment)

Performance: #126 (comment)

Companion PRs
plotly/dash-core-components#478
plotly/dash-html-components#99
https://github.com/plotly/dash-docs/pull/439
plotly/dash-component-boilerplate#65

Marc-André Rivet added 2 commits February 27, 2019 13:34
- always update self
- only update Dash if an updated prop is listened to
Comment threadsrc/components/core/NotifyObservers.react.js Outdated
}
return children;
function NotifyObserversComponent({ children, setProps }) {
return React.cloneElement(children, { setProps });

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Always pass the setProps function to the component. It will censure itself as needed.

dependency.inputs.find(input => input.id === ownProps.id && input.property === key) ||
dependency.state.find(state => state.id === ownProps.id && state.property === key)
)
)(keysIn(newProps));

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

From all the props updated, find the ones listened for

id: ownProps.id,
props: pick(watchedKeys)(newProps)
}));
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Only dispatch to Dash if at least one watched prop is updated

itempath: stateProps.paths[ownProps.id],
};
itempath: stateProps.paths[ownProps.id]
}));

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The component used to only be updated when it had a watched prop -- this makes no sense as it prevents components from updating themselves and have consistent behavior in the FE in all usage scenarios.

Comment threadtests/test_render.py Outdated
@alexcjohnson

Copy link
Copy Markdown
Collaborator

This looks like a good idea to me, but I'm not sure I'm aware of all the possible implications. @T4rk1n would you mind taking a look?

@Marc-Andre-RivetMarc-Andre-Rivet changed the title Change renderer setProps assignation logic[WIP] Change renderer setProps assignation logicFeb 27, 2019
@chriddyp

Copy link
Copy Markdown
Member

Only dispatch to Dash if at least one watched prop is updated

I think this implementation makes sense. I originally made "setProps" conditional for dcc.GraphhoverData - if someone was just displaying a graph but not listening to its updates, I didn't want to subscribe the event handler for the graph and have it cause the entire tree to re-render on every hover:
https://github.com/plotly/dash-core-components/blob/83a3ea07fd8af9ee3ca57c8da8ed32483e96f87c/src/components/Graph.react.js#L134-L141

In this PR, if you're only re-rendering the component if it is part of a callback, then there shouldn't be any adverse performance effects 👌


On another note, from a documentation point of view, we'll want to update our 'react for python devs' guide (https://dash.plot.ly/react-for-python-developers) as well as the boilerplate. Should mostly just be a lot of 🔪 though 😸

Marc-André Rivet added 3 commits March 1, 2019 13:32
# Conflicts:
#	src/components/core/NotifyObservers.react.js
- memoize + PureComponent TreeContainer
@Marc-Andre-Rivet

Marc-Andre-Rivet commented Mar 2, 2019

Copy link
Copy Markdown
ContributorAuthor

setProps being always present triggers a significant number of additional updates for many scenarios, also setProps and loading_states being evaluated on each render creates a new function and new object, forcing re-renders -- modified the PR with a very experimental attempt at limiting the re-renders at the source and simplifying the tree structure / logic -- I'll retag reviewers once I feel I've (1) addressed the performance concerns, (2) verified it works in known scenarios

Comment threadsrc/TreeContainer.js Outdated
isEqualArgs(lastArgs, args) ?
lastResult :
(lastArgs = args) && (lastResult = fn(...args));
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

memoize and equality code copied over from the table for experimenting

Comment threadsrc/TreeContainer.js Outdated
@Marc-Andre-Rivet

Copy link
Copy Markdown
ContributorAuthor

Simplified the changes -- since the renders are mostly driven through changes to immutable values in the store, PureComponents + memoization actually provides no benefit.

Prepended renderer centric props with _dashprivate_.

@Marc-Andre-RivetMarc-Andre-Rivet changed the title [WIP] Change renderer setProps assignation logicChange renderer setProps assignation logicMar 5, 2019
@Marc-Andre-Rivet

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson I think this is ready for another look.
@chriddyp Did you have a chance to try it out against an existing / larger app?

Comment threadsrc/TreeContainer.js
Comment threadsrc/TreeContainer.js Outdated
Comment threadsrc/TreeContainer.js Outdated

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm happy with this 💃
@chriddyp did you still want to try it on a big app?

@chriddyp

Copy link
Copy Markdown
Member

did you still want to try it on a big app?

I shared the source of a private large app with @Marc-Andre-Rivet to test things out with. I'm 💃 either way.

Also note that there are a few places in the documentation that we should update once this is released:

@Marc-Andre-Rivet

Marc-Andre-Rivet commented Mar 11, 2019

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson@chriddyp

Did some actual performance tests, mostly against the dash-docs. Got the demo app working partially but against the latest version of dash but there were still multiple issues, fixed at lot of them and got the code running but in a weird state -- and not sure how significant the results really are.

In all cases, opening a new browser window for each version, the runs are all in the same tab. Version ordering changes randomly. One warm up run prior to taking results.

In the docs' dcc page, simply reloading the page gave the following results in milliseconds for 5 reloads:
Dash 0.39: 6046, 6228, 6135
Modified: 4667, 4653,4635
The sample is small but ~25% difference on 15 runs is probably significant.

Table paging page, clicking prev/nex 10 times each, in milliseconds:
Dash 0.39: 3398, 3421, 3223
Modified: 637, 640, 604
5-to-1 difference... this might be partially due to no callback requiring data

Table editing, modifying 10 cells per run, in milliseconds:
Dash 0.39: 2647, 2418, 2439
Modified: 1059, 1109, 1113
2-to-1 difference

The Graph example w/ hover, 20 hovers, in milliseconds:
Dash 0.39: 833, 385, 512
Modified: 671, 750, 588
Nothing significant with this set -- the variability is high.. could go either way I guess -- could do more runs to make sure.

The Graph example w/ hover, 5 page loads, in milliseconds:
Dash 0.39: 1996, 2006, 2104
Modified: 2037, 2073, 2009
Nothing significant with this set

It seems that at worse this has no impact for certain components (e.g. Graph) and that at best, for certain interactions the performance impact can be very significant.

@chriddyp

Copy link
Copy Markdown
Member

Nice, those look encouraging. Another page I just thought of is https://dash.plot.ly/all. It's a "hidden" page that we used to generate a PDF version of the docs. It loads all of the pages in the docs at once (takes like 40 seconds to load), probably the most render intensive page that I can think of.

@Marc-Andre-Rivet

Marc-Andre-Rivet commented Mar 11, 2019

Copy link
Copy Markdown
ContributorAuthor

Dry run from a new tab of /all gave:

Dash 0.39: "Updating..." disappeared after ~ 229 seconds
Dev tools crashed when trying to load the performance analysis 😁

Modified: "Updating..." disappeared after 17.2, 17.4 and 18.1 seconds

@Marc-Andre-Rivet

Copy link
Copy Markdown
ContributorAuthor

2 step merge -- will remove the refs for the feature branches of dcc and html right after tests pass everywhere

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unregistered dcc input components are having their contents cleared after callback updates

3 participants

@Marc-Andre-Rivet@alexcjohnson@chriddyp
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Aug 29, 2025. It is now read-only.

Change renderer setProps assignation logic - #126

Merged
Marc-Andre-Rivet merged 28 commits into
masterfrom
set-props-callback
Mar 11, 2019
Merged

Change renderer setProps assignation logic#126
Marc-Andre-Rivet merged 28 commits into
masterfrom
set-props-callback

Conversation

@Marc-Andre-Rivet

@Marc-Andre-RivetMarc-Andre-Rivet commented Feb 27, 2019

Copy link
Copy Markdown
Contributor

Fixes#40.
(Supersedes) Fixeshttps://github.com/plotly/dash-docs/issues/352.
(Supersedes) Fixeshttps://github.com/plotly/dash-docs/issues/435

Currently setProps is passed to the components only if a component property is used as an input or a state in a callback. This causes two problems:

  • components do not update as expected as the props inside the component itself are only updated if setProps exists:
    here we set the own-props


    here we decide if there's a setProps

  • the table is in its own special land as it loops back on itself if it doesn't have a setProps (uses its own internal state to update itself) -- which is fine until the following happens: a callback updates the table AND no callback requires a table prop as input / state -- the table now updates itself into its state and receives updates into its props -- for reasons not detailed here, the table merges states unto props, overriding props in the process -- the net result is that the props update from the callbacks are overwritten by the table's state as in updating a datatable with editable=True dash-table#386

(the table will still need its loopback with this fix for standalone purposes)

Additionally, the current renderer logic is very permissive as to which props will be sent. If a prop is used as input / state, each time a prop is updated, all updated props will be sent to Dash. For most components the overhead is a minor nuisance but for the table, updating data and sending it back to Dash, if not required, is a major overhead, possibly orders of magnitude larger than the useful data.

This PR proposes a solution to both problems:

  • always pass setProps to the component
  • on setProps: (1) always update the component itself -- no more stale updates that are impossible to understand for users -- this is seriously probably the most frequent question I answer with "create a bogus callback on your prop as a temporary solution", (2) filter out the props that are not asked for by Dash

Problems that arise from this seem to mostly be related to the way we test..

  • since we now update the components correctly all the time, the layout contains additional props, some of which are time sensitive (e.g. n_click_timestamp) -- I've adjusted the tests so as to be less dependent on the layout details
  • this needs to be used in tests from dcc, html, dash, table before going forward
  • check performance impact (see Chris' comment)

Performance: #126 (comment)

Companion PRs
plotly/dash-core-components#478
plotly/dash-html-components#99
https://github.com/plotly/dash-docs/pull/439
plotly/dash-component-boilerplate#65

Marc-André Rivet added 2 commits February 27, 2019 13:34
- always update self
- only update Dash if an updated prop is listened to
Comment threadsrc/components/core/NotifyObservers.react.js Outdated
}
return children;
function NotifyObserversComponent({ children, setProps }) {
return React.cloneElement(children, { setProps });

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Always pass the setProps function to the component. It will censure itself as needed.

dependency.inputs.find(input => input.id === ownProps.id && input.property === key) ||
dependency.state.find(state => state.id === ownProps.id && state.property === key)
)
)(keysIn(newProps));

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

From all the props updated, find the ones listened for

id: ownProps.id,
props: pick(watchedKeys)(newProps)
}));
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Only dispatch to Dash if at least one watched prop is updated

itempath: stateProps.paths[ownProps.id],
};
itempath: stateProps.paths[ownProps.id]
}));

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The component used to only be updated when it had a watched prop -- this makes no sense as it prevents components from updating themselves and have consistent behavior in the FE in all usage scenarios.

Comment threadtests/test_render.py Outdated
@alexcjohnson

Copy link
Copy Markdown
Collaborator

This looks like a good idea to me, but I'm not sure I'm aware of all the possible implications. @T4rk1n would you mind taking a look?

@Marc-Andre-RivetMarc-Andre-Rivet changed the title Change renderer setProps assignation logic[WIP] Change renderer setProps assignation logicFeb 27, 2019
@chriddyp

Copy link
Copy Markdown
Member

Only dispatch to Dash if at least one watched prop is updated

I think this implementation makes sense. I originally made "setProps" conditional for dcc.GraphhoverData - if someone was just displaying a graph but not listening to its updates, I didn't want to subscribe the event handler for the graph and have it cause the entire tree to re-render on every hover:
https://github.com/plotly/dash-core-components/blob/83a3ea07fd8af9ee3ca57c8da8ed32483e96f87c/src/components/Graph.react.js#L134-L141

In this PR, if you're only re-rendering the component if it is part of a callback, then there shouldn't be any adverse performance effects 👌


On another note, from a documentation point of view, we'll want to update our 'react for python devs' guide (https://dash.plot.ly/react-for-python-developers) as well as the boilerplate. Should mostly just be a lot of 🔪 though 😸

Marc-André Rivet added 3 commits March 1, 2019 13:32
# Conflicts:
#	src/components/core/NotifyObservers.react.js
- memoize + PureComponent TreeContainer
@Marc-Andre-Rivet

Marc-Andre-Rivet commented Mar 2, 2019

Copy link
Copy Markdown
ContributorAuthor

setProps being always present triggers a significant number of additional updates for many scenarios, also setProps and loading_states being evaluated on each render creates a new function and new object, forcing re-renders -- modified the PR with a very experimental attempt at limiting the re-renders at the source and simplifying the tree structure / logic -- I'll retag reviewers once I feel I've (1) addressed the performance concerns, (2) verified it works in known scenarios

Comment threadsrc/TreeContainer.js Outdated
isEqualArgs(lastArgs, args) ?
lastResult :
(lastArgs = args) && (lastResult = fn(...args));
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

memoize and equality code copied over from the table for experimenting

Comment threadsrc/TreeContainer.js Outdated
@Marc-Andre-Rivet

Copy link
Copy Markdown
ContributorAuthor

Simplified the changes -- since the renders are mostly driven through changes to immutable values in the store, PureComponents + memoization actually provides no benefit.

Prepended renderer centric props with _dashprivate_.

@Marc-Andre-RivetMarc-Andre-Rivet changed the title [WIP] Change renderer setProps assignation logicChange renderer setProps assignation logicMar 5, 2019
@Marc-Andre-Rivet

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson I think this is ready for another look.
@chriddyp Did you have a chance to try it out against an existing / larger app?

Comment threadsrc/TreeContainer.js
Comment threadsrc/TreeContainer.js Outdated
Comment threadsrc/TreeContainer.js Outdated

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm happy with this 💃
@chriddyp did you still want to try it on a big app?

@chriddyp

Copy link
Copy Markdown
Member

did you still want to try it on a big app?

I shared the source of a private large app with @Marc-Andre-Rivet to test things out with. I'm 💃 either way.

Also note that there are a few places in the documentation that we should update once this is released:

@Marc-Andre-Rivet

Marc-Andre-Rivet commented Mar 11, 2019

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson@chriddyp

Did some actual performance tests, mostly against the dash-docs. Got the demo app working partially but against the latest version of dash but there were still multiple issues, fixed at lot of them and got the code running but in a weird state -- and not sure how significant the results really are.

In all cases, opening a new browser window for each version, the runs are all in the same tab. Version ordering changes randomly. One warm up run prior to taking results.

In the docs' dcc page, simply reloading the page gave the following results in milliseconds for 5 reloads:
Dash 0.39: 6046, 6228, 6135
Modified: 4667, 4653,4635
The sample is small but ~25% difference on 15 runs is probably significant.

Table paging page, clicking prev/nex 10 times each, in milliseconds:
Dash 0.39: 3398, 3421, 3223
Modified: 637, 640, 604
5-to-1 difference... this might be partially due to no callback requiring data

Table editing, modifying 10 cells per run, in milliseconds:
Dash 0.39: 2647, 2418, 2439
Modified: 1059, 1109, 1113
2-to-1 difference

The Graph example w/ hover, 20 hovers, in milliseconds:
Dash 0.39: 833, 385, 512
Modified: 671, 750, 588
Nothing significant with this set -- the variability is high.. could go either way I guess -- could do more runs to make sure.

The Graph example w/ hover, 5 page loads, in milliseconds:
Dash 0.39: 1996, 2006, 2104
Modified: 2037, 2073, 2009
Nothing significant with this set

It seems that at worse this has no impact for certain components (e.g. Graph) and that at best, for certain interactions the performance impact can be very significant.

@chriddyp

Copy link
Copy Markdown
Member

Nice, those look encouraging. Another page I just thought of is https://dash.plot.ly/all. It's a "hidden" page that we used to generate a PDF version of the docs. It loads all of the pages in the docs at once (takes like 40 seconds to load), probably the most render intensive page that I can think of.

@Marc-Andre-Rivet

Marc-Andre-Rivet commented Mar 11, 2019

Copy link
Copy Markdown
ContributorAuthor

Dry run from a new tab of /all gave:

Dash 0.39: "Updating..." disappeared after ~ 229 seconds
Dev tools crashed when trying to load the performance analysis 😁

Modified: "Updating..." disappeared after 17.2, 17.4 and 18.1 seconds

@Marc-Andre-Rivet

Copy link
Copy Markdown
ContributorAuthor

2 step merge -- will remove the refs for the feature branches of dcc and html right after tests pass everywhere

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unregistered dcc input components are having their contents cleared after callback updates

3 participants

@Marc-Andre-Rivet@alexcjohnson@chriddyp
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Aug 29, 2025. It is now read-only.

Change renderer setProps assignation logic - #126

Merged
Marc-Andre-Rivet merged 28 commits into
masterfrom
set-props-callback
Mar 11, 2019
Merged

Change renderer setProps assignation logic#126
Marc-Andre-Rivet merged 28 commits into
masterfrom
set-props-callback

Conversation

@Marc-Andre-Rivet

@Marc-Andre-RivetMarc-Andre-Rivet commented Feb 27, 2019

Copy link
Copy Markdown
Contributor

Fixes#40.
(Supersedes) Fixeshttps://github.com/plotly/dash-docs/issues/352.
(Supersedes) Fixeshttps://github.com/plotly/dash-docs/issues/435

Currently setProps is passed to the components only if a component property is used as an input or a state in a callback. This causes two problems:

  • components do not update as expected as the props inside the component itself are only updated if setProps exists:
    here we set the own-props


    here we decide if there's a setProps

  • the table is in its own special land as it loops back on itself if it doesn't have a setProps (uses its own internal state to update itself) -- which is fine until the following happens: a callback updates the table AND no callback requires a table prop as input / state -- the table now updates itself into its state and receives updates into its props -- for reasons not detailed here, the table merges states unto props, overriding props in the process -- the net result is that the props update from the callbacks are overwritten by the table's state as in updating a datatable with editable=True dash-table#386

(the table will still need its loopback with this fix for standalone purposes)

Additionally, the current renderer logic is very permissive as to which props will be sent. If a prop is used as input / state, each time a prop is updated, all updated props will be sent to Dash. For most components the overhead is a minor nuisance but for the table, updating data and sending it back to Dash, if not required, is a major overhead, possibly orders of magnitude larger than the useful data.

This PR proposes a solution to both problems:

  • always pass setProps to the component
  • on setProps: (1) always update the component itself -- no more stale updates that are impossible to understand for users -- this is seriously probably the most frequent question I answer with "create a bogus callback on your prop as a temporary solution", (2) filter out the props that are not asked for by Dash

Problems that arise from this seem to mostly be related to the way we test..

  • since we now update the components correctly all the time, the layout contains additional props, some of which are time sensitive (e.g. n_click_timestamp) -- I've adjusted the tests so as to be less dependent on the layout details
  • this needs to be used in tests from dcc, html, dash, table before going forward
  • check performance impact (see Chris' comment)

Performance: #126 (comment)

Companion PRs
plotly/dash-core-components#478
plotly/dash-html-components#99
https://github.com/plotly/dash-docs/pull/439
plotly/dash-component-boilerplate#65

Marc-André Rivet added 2 commits February 27, 2019 13:34
- always update self
- only update Dash if an updated prop is listened to
Comment threadsrc/components/core/NotifyObservers.react.js Outdated
}
return children;
function NotifyObserversComponent({ children, setProps }) {
return React.cloneElement(children, { setProps });

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Always pass the setProps function to the component. It will censure itself as needed.

dependency.inputs.find(input => input.id === ownProps.id && input.property === key) ||
dependency.state.find(state => state.id === ownProps.id && state.property === key)
)
)(keysIn(newProps));

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

From all the props updated, find the ones listened for

id: ownProps.id,
props: pick(watchedKeys)(newProps)
}));
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Only dispatch to Dash if at least one watched prop is updated

itempath: stateProps.paths[ownProps.id],
};
itempath: stateProps.paths[ownProps.id]
}));

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The component used to only be updated when it had a watched prop -- this makes no sense as it prevents components from updating themselves and have consistent behavior in the FE in all usage scenarios.

Comment threadtests/test_render.py Outdated
@alexcjohnson

Copy link
Copy Markdown
Collaborator

This looks like a good idea to me, but I'm not sure I'm aware of all the possible implications. @T4rk1n would you mind taking a look?

@Marc-Andre-RivetMarc-Andre-Rivet changed the title Change renderer setProps assignation logic[WIP] Change renderer setProps assignation logicFeb 27, 2019
@chriddyp

Copy link
Copy Markdown
Member

Only dispatch to Dash if at least one watched prop is updated

I think this implementation makes sense. I originally made "setProps" conditional for dcc.GraphhoverData - if someone was just displaying a graph but not listening to its updates, I didn't want to subscribe the event handler for the graph and have it cause the entire tree to re-render on every hover:
https://github.com/plotly/dash-core-components/blob/83a3ea07fd8af9ee3ca57c8da8ed32483e96f87c/src/components/Graph.react.js#L134-L141

In this PR, if you're only re-rendering the component if it is part of a callback, then there shouldn't be any adverse performance effects 👌


On another note, from a documentation point of view, we'll want to update our 'react for python devs' guide (https://dash.plot.ly/react-for-python-developers) as well as the boilerplate. Should mostly just be a lot of 🔪 though 😸

Marc-André Rivet added 3 commits March 1, 2019 13:32
# Conflicts:
#	src/components/core/NotifyObservers.react.js
- memoize + PureComponent TreeContainer
@Marc-Andre-Rivet

Marc-Andre-Rivet commented Mar 2, 2019

Copy link
Copy Markdown
ContributorAuthor

setProps being always present triggers a significant number of additional updates for many scenarios, also setProps and loading_states being evaluated on each render creates a new function and new object, forcing re-renders -- modified the PR with a very experimental attempt at limiting the re-renders at the source and simplifying the tree structure / logic -- I'll retag reviewers once I feel I've (1) addressed the performance concerns, (2) verified it works in known scenarios

Comment threadsrc/TreeContainer.js Outdated
isEqualArgs(lastArgs, args) ?
lastResult :
(lastArgs = args) && (lastResult = fn(...args));
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

memoize and equality code copied over from the table for experimenting

Comment threadsrc/TreeContainer.js Outdated
@Marc-Andre-Rivet

Copy link
Copy Markdown
ContributorAuthor

Simplified the changes -- since the renders are mostly driven through changes to immutable values in the store, PureComponents + memoization actually provides no benefit.

Prepended renderer centric props with _dashprivate_.

@Marc-Andre-RivetMarc-Andre-Rivet changed the title [WIP] Change renderer setProps assignation logicChange renderer setProps assignation logicMar 5, 2019
@Marc-Andre-Rivet

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson I think this is ready for another look.
@chriddyp Did you have a chance to try it out against an existing / larger app?

Comment threadsrc/TreeContainer.js
Comment threadsrc/TreeContainer.js Outdated
Comment threadsrc/TreeContainer.js Outdated

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm happy with this 💃
@chriddyp did you still want to try it on a big app?

@chriddyp

Copy link
Copy Markdown
Member

did you still want to try it on a big app?

I shared the source of a private large app with @Marc-Andre-Rivet to test things out with. I'm 💃 either way.

Also note that there are a few places in the documentation that we should update once this is released:

@Marc-Andre-Rivet

Marc-Andre-Rivet commented Mar 11, 2019

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson@chriddyp

Did some actual performance tests, mostly against the dash-docs. Got the demo app working partially but against the latest version of dash but there were still multiple issues, fixed at lot of them and got the code running but in a weird state -- and not sure how significant the results really are.

In all cases, opening a new browser window for each version, the runs are all in the same tab. Version ordering changes randomly. One warm up run prior to taking results.

In the docs' dcc page, simply reloading the page gave the following results in milliseconds for 5 reloads:
Dash 0.39: 6046, 6228, 6135
Modified: 4667, 4653,4635
The sample is small but ~25% difference on 15 runs is probably significant.

Table paging page, clicking prev/nex 10 times each, in milliseconds:
Dash 0.39: 3398, 3421, 3223
Modified: 637, 640, 604
5-to-1 difference... this might be partially due to no callback requiring data

Table editing, modifying 10 cells per run, in milliseconds:
Dash 0.39: 2647, 2418, 2439
Modified: 1059, 1109, 1113
2-to-1 difference

The Graph example w/ hover, 20 hovers, in milliseconds:
Dash 0.39: 833, 385, 512
Modified: 671, 750, 588
Nothing significant with this set -- the variability is high.. could go either way I guess -- could do more runs to make sure.

The Graph example w/ hover, 5 page loads, in milliseconds:
Dash 0.39: 1996, 2006, 2104
Modified: 2037, 2073, 2009
Nothing significant with this set

It seems that at worse this has no impact for certain components (e.g. Graph) and that at best, for certain interactions the performance impact can be very significant.

@chriddyp

Copy link
Copy Markdown
Member

Nice, those look encouraging. Another page I just thought of is https://dash.plot.ly/all. It's a "hidden" page that we used to generate a PDF version of the docs. It loads all of the pages in the docs at once (takes like 40 seconds to load), probably the most render intensive page that I can think of.

@Marc-Andre-Rivet

Marc-Andre-Rivet commented Mar 11, 2019

Copy link
Copy Markdown
ContributorAuthor

Dry run from a new tab of /all gave:

Dash 0.39: "Updating..." disappeared after ~ 229 seconds
Dev tools crashed when trying to load the performance analysis 😁

Modified: "Updating..." disappeared after 17.2, 17.4 and 18.1 seconds

@Marc-Andre-Rivet

Copy link
Copy Markdown
ContributorAuthor

2 step merge -- will remove the refs for the feature branches of dcc and html right after tests pass everywhere

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unregistered dcc input components are having their contents cleared after callback updates

3 participants

@Marc-Andre-Rivet@alexcjohnson@chriddyp
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
This repository was archived by the owner on Aug 29, 2025. It is now read-only.

Change renderer setProps assignation logic - #126

Merged
Marc-Andre-Rivet merged 28 commits into
masterfrom
set-props-callback
Mar 11, 2019
Merged

Change renderer setProps assignation logic#126
Marc-Andre-Rivet merged 28 commits into
masterfrom
set-props-callback

Conversation

@Marc-Andre-Rivet

@Marc-Andre-RivetMarc-Andre-Rivet commented Feb 27, 2019

Copy link
Copy Markdown
Contributor

Fixes#40.
(Supersedes) Fixeshttps://github.com/plotly/dash-docs/issues/352.
(Supersedes) Fixeshttps://github.com/plotly/dash-docs/issues/435

Currently setProps is passed to the components only if a component property is used as an input or a state in a callback. This causes two problems:

  • components do not update as expected as the props inside the component itself are only updated if setProps exists:
    here we set the own-props


    here we decide if there's a setProps

  • the table is in its own special land as it loops back on itself if it doesn't have a setProps (uses its own internal state to update itself) -- which is fine until the following happens: a callback updates the table AND no callback requires a table prop as input / state -- the table now updates itself into its state and receives updates into its props -- for reasons not detailed here, the table merges states unto props, overriding props in the process -- the net result is that the props update from the callbacks are overwritten by the table's state as in updating a datatable with editable=True dash-table#386

(the table will still need its loopback with this fix for standalone purposes)

Additionally, the current renderer logic is very permissive as to which props will be sent. If a prop is used as input / state, each time a prop is updated, all updated props will be sent to Dash. For most components the overhead is a minor nuisance but for the table, updating data and sending it back to Dash, if not required, is a major overhead, possibly orders of magnitude larger than the useful data.

This PR proposes a solution to both problems:

  • always pass setProps to the component
  • on setProps: (1) always update the component itself -- no more stale updates that are impossible to understand for users -- this is seriously probably the most frequent question I answer with "create a bogus callback on your prop as a temporary solution", (2) filter out the props that are not asked for by Dash

Problems that arise from this seem to mostly be related to the way we test..

  • since we now update the components correctly all the time, the layout contains additional props, some of which are time sensitive (e.g. n_click_timestamp) -- I've adjusted the tests so as to be less dependent on the layout details
  • this needs to be used in tests from dcc, html, dash, table before going forward
  • check performance impact (see Chris' comment)

Performance: #126 (comment)

Companion PRs
plotly/dash-core-components#478
plotly/dash-html-components#99
https://github.com/plotly/dash-docs/pull/439
plotly/dash-component-boilerplate#65

Marc-André Rivet added 2 commits February 27, 2019 13:34
- always update self
- only update Dash if an updated prop is listened to
Comment threadsrc/components/core/NotifyObservers.react.js Outdated
}
return children;
function NotifyObserversComponent({ children, setProps }) {
return React.cloneElement(children, { setProps });

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Always pass the setProps function to the component. It will censure itself as needed.

dependency.inputs.find(input => input.id === ownProps.id && input.property === key) ||
dependency.state.find(state => state.id === ownProps.id && state.property === key)
)
)(keysIn(newProps));

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

From all the props updated, find the ones listened for

id: ownProps.id,
props: pick(watchedKeys)(newProps)
}));
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Only dispatch to Dash if at least one watched prop is updated

itempath: stateProps.paths[ownProps.id],
};
itempath: stateProps.paths[ownProps.id]
}));

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The component used to only be updated when it had a watched prop -- this makes no sense as it prevents components from updating themselves and have consistent behavior in the FE in all usage scenarios.

Comment threadtests/test_render.py Outdated
@alexcjohnson

Copy link
Copy Markdown
Collaborator

This looks like a good idea to me, but I'm not sure I'm aware of all the possible implications. @T4rk1n would you mind taking a look?

@Marc-Andre-RivetMarc-Andre-Rivet changed the title Change renderer setProps assignation logic[WIP] Change renderer setProps assignation logicFeb 27, 2019
@chriddyp

Copy link
Copy Markdown
Member

Only dispatch to Dash if at least one watched prop is updated

I think this implementation makes sense. I originally made "setProps" conditional for dcc.GraphhoverData - if someone was just displaying a graph but not listening to its updates, I didn't want to subscribe the event handler for the graph and have it cause the entire tree to re-render on every hover:
https://github.com/plotly/dash-core-components/blob/83a3ea07fd8af9ee3ca57c8da8ed32483e96f87c/src/components/Graph.react.js#L134-L141

In this PR, if you're only re-rendering the component if it is part of a callback, then there shouldn't be any adverse performance effects 👌


On another note, from a documentation point of view, we'll want to update our 'react for python devs' guide (https://dash.plot.ly/react-for-python-developers) as well as the boilerplate. Should mostly just be a lot of 🔪 though 😸

Marc-André Rivet added 3 commits March 1, 2019 13:32
# Conflicts:
#	src/components/core/NotifyObservers.react.js
- memoize + PureComponent TreeContainer
@Marc-Andre-Rivet

Marc-Andre-Rivet commented Mar 2, 2019

Copy link
Copy Markdown
ContributorAuthor

setProps being always present triggers a significant number of additional updates for many scenarios, also setProps and loading_states being evaluated on each render creates a new function and new object, forcing re-renders -- modified the PR with a very experimental attempt at limiting the re-renders at the source and simplifying the tree structure / logic -- I'll retag reviewers once I feel I've (1) addressed the performance concerns, (2) verified it works in known scenarios

Comment threadsrc/TreeContainer.js Outdated
isEqualArgs(lastArgs, args) ?
lastResult :
(lastArgs = args) && (lastResult = fn(...args));
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

memoize and equality code copied over from the table for experimenting

Comment threadsrc/TreeContainer.js Outdated
@Marc-Andre-Rivet

Copy link
Copy Markdown
ContributorAuthor

Simplified the changes -- since the renders are mostly driven through changes to immutable values in the store, PureComponents + memoization actually provides no benefit.

Prepended renderer centric props with _dashprivate_.

@Marc-Andre-RivetMarc-Andre-Rivet changed the title [WIP] Change renderer setProps assignation logicChange renderer setProps assignation logicMar 5, 2019
@Marc-Andre-Rivet

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson I think this is ready for another look.
@chriddyp Did you have a chance to try it out against an existing / larger app?

Comment threadsrc/TreeContainer.js
Comment threadsrc/TreeContainer.js Outdated
Comment threadsrc/TreeContainer.js Outdated

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm happy with this 💃
@chriddyp did you still want to try it on a big app?

@chriddyp

Copy link
Copy Markdown
Member

did you still want to try it on a big app?

I shared the source of a private large app with @Marc-Andre-Rivet to test things out with. I'm 💃 either way.

Also note that there are a few places in the documentation that we should update once this is released:

@Marc-Andre-Rivet

Marc-Andre-Rivet commented Mar 11, 2019

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson@chriddyp

Did some actual performance tests, mostly against the dash-docs. Got the demo app working partially but against the latest version of dash but there were still multiple issues, fixed at lot of them and got the code running but in a weird state -- and not sure how significant the results really are.

In all cases, opening a new browser window for each version, the runs are all in the same tab. Version ordering changes randomly. One warm up run prior to taking results.

In the docs' dcc page, simply reloading the page gave the following results in milliseconds for 5 reloads:
Dash 0.39: 6046, 6228, 6135
Modified: 4667, 4653,4635
The sample is small but ~25% difference on 15 runs is probably significant.

Table paging page, clicking prev/nex 10 times each, in milliseconds:
Dash 0.39: 3398, 3421, 3223
Modified: 637, 640, 604
5-to-1 difference... this might be partially due to no callback requiring data

Table editing, modifying 10 cells per run, in milliseconds:
Dash 0.39: 2647, 2418, 2439
Modified: 1059, 1109, 1113
2-to-1 difference

The Graph example w/ hover, 20 hovers, in milliseconds:
Dash 0.39: 833, 385, 512
Modified: 671, 750, 588
Nothing significant with this set -- the variability is high.. could go either way I guess -- could do more runs to make sure.

The Graph example w/ hover, 5 page loads, in milliseconds:
Dash 0.39: 1996, 2006, 2104
Modified: 2037, 2073, 2009
Nothing significant with this set

It seems that at worse this has no impact for certain components (e.g. Graph) and that at best, for certain interactions the performance impact can be very significant.

@chriddyp

Copy link
Copy Markdown
Member

Nice, those look encouraging. Another page I just thought of is https://dash.plot.ly/all. It's a "hidden" page that we used to generate a PDF version of the docs. It loads all of the pages in the docs at once (takes like 40 seconds to load), probably the most render intensive page that I can think of.

@Marc-Andre-Rivet

Marc-Andre-Rivet commented Mar 11, 2019

Copy link
Copy Markdown
ContributorAuthor

Dry run from a new tab of /all gave:

Dash 0.39: "Updating..." disappeared after ~ 229 seconds
Dev tools crashed when trying to load the performance analysis 😁

Modified: "Updating..." disappeared after 17.2, 17.4 and 18.1 seconds

@Marc-Andre-Rivet

Copy link
Copy Markdown
ContributorAuthor

2 step merge -- will remove the refs for the feature branches of dcc and html right after tests pass everywhere

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unregistered dcc input components are having their contents cleared after callback updates

3 participants

@Marc-Andre-Rivet@alexcjohnson@chriddyp