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

Loading states api - #93

Merged
valentijnnieman merged 48 commits into
masterfrom
loading_states_api
Feb 28, 2019
Merged

Loading states api#93
valentijnnieman merged 48 commits into
masterfrom
loading_states_api

Conversation

@valentijnnieman

@valentijnniemanvalentijnnieman commented Oct 30, 2018

Copy link
Copy Markdown
Contributor

Here's the first pass at a loading states api, where dash-renderer now provides a loading_state prop to components that are taking some time to load. It is used with a dist of this PR that provides a Loading component, that can wrap any other Dash component, making it display a loading spinner if it's not fully rendered. An example is included in simple.py, make sure to install the dev-requirements.txt before running it!

TODO:

  • Include which property is loading. Currently we strip the component's name, but it also includes the property that's loading, so we could pass that down as well.
  • Clean-up!

The loading_state prop is an object that looks like:

{
is_loading: bool,
component_name: string,
prop_name: string
}

So that component authors can determine when to show a loading spinner, and know which component in the chain caused the loading to happen (it will return the id of the component), and which prop is loading (for example children).

Community post: https://community.plot.ly/t/loading-states-api-and-a-loading-component-prerelease

Comment threadsrc/APIController.react.js Outdated
@valentijnniemanvalentijnnieman changed the title [WIP] Loading states apiLoading states apiNov 6, 2018
@chriddyp

Copy link
Copy Markdown
Member

For those following along in the community, here's a demo:
image
loading states

if (r.status === 'loading' && contains(id, r.controllerId)) {
isLoading = true;
loadingComponent = r.controllerId.split('.')[0];
loadingProp = r.controllerId.split('.')[1];

@T4rk1nT4rk1nNov 26, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can split only one time: [loadingProp, loadingComponent] = r.controllerId.split('.').

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Had a hard time figuring this out, that is not a proper map. It should be a for loop or a filter or a find.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the most appropriate would be a .forEach()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We have been using rambda elsewhere in dash-renderer even when a core javascript function exists for a task, so https://ramdajs.com/docs/#forEach would fit better

}
});

const thisRequest = requestQueue.filter(r => contains(id, r.controllerId));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The filter is here.

Comment threadpackage.json Outdated
@Marc-Andre-Rivet

Marc-Andre-Rivet commented Jan 8, 2019

Copy link
Copy Markdown
Contributor

Would like to see the following issues opened as follow ups:

  • enabling direct support of loading state in DCC components (loading_state + animation type)
  • integration testing support for dash-renderer (our tests are all at the behavioural / dash server level atm) -- test loading states, test loading states transition, test more complex graph shapes (Loading component with multiple children, Loading comp within a parent loading comp)
  • removing the 'Loading...' from dash.py

@Marc-Andre-Rivet

Marc-Andre-Rivet commented Jan 24, 2019

Copy link
Copy Markdown
Contributor

As discussed with @valentijnnieman, we are currently going for a mixed approach, supporting both

  • loading state as a data-attribute on the components + styleguide / css styling of loading comps
  • Loading component with 1 level deep loading_state support -- the user becomes responsible for putting items that are actually slow to load (e.g. no listening to children prop in Dash)

While the updated implementation looks promising, my main concern right now has to do with the mechanics of getting this to work -- the TreeContainer or the NotifyObserver needs to be hooked up to the store's requestQueue through a prop in order to determine the component's state -- the queue is impacted by user actions and changes often during back-and-forth with Dash. Since not all components are pure in html and dcc, or protected by a componentShouldUpdate method, this may cause a significant performance impact. This should be studied and minimized before the feature can be released / sent back for community testing. It's possible that the impact will be negligible but if not, we may have to expose the queue data differently so as to not trigger useless re-render.

@chriddyp

Copy link
Copy Markdown
Member

the TreeContainer or the NotifyObserver needs to be hooked up to the store's requestQueue through a prop in order to determine the component's state

I'm a little rusty on the redux details here, but I thought that any dispatch that happens will trigger an update. I don't believe that there isn't anything in NotifyObservers that says "If store.x changes, then update. But if store.y changes, don't update.". Does this match your understanding too or am I forgetting about something? If so, can you point me to the code where this happens?

So, if my understanding is right, the existing code is dispatching to update the request queue which is already cause re-rendering. Passing new props through in NotifyObserversComponent shouldn't change this.

Code reference: dispatch(setRequestQueue(

dispatch(setRequestQueue(concat(requestQueue,newRequestQueue)));

Connected NotifyObservers

/*
* NotifyObservers passes a connected `setProps` handler down to
* its child as a prop
*/
functionmapStateToProps(state){
return{
dependencies: state.dependenciesRequest.content,
paths: state.paths,
};
}
functionmapDispatchToProps(dispatch){
return{dispatch};
}
functionmergeProps(stateProps,dispatchProps,ownProps){
const{dispatch}=dispatchProps;
return{
id: ownProps.id,
children: ownProps.children,
dependencies: stateProps.dependencies,
paths: stateProps.paths,
setProps: functionsetProps(newProps){
constpayload={
props: newProps,
id: ownProps.id,
itempath: stateProps.paths[ownProps.id],
};
// Update this component's props
dispatch(updateProps(payload));
// Update output components that depend on this input
dispatch(notifyObservers({id: ownProps.id,props: newProps}));
},
};
}
functionNotifyObserversComponent({
children,
id,
paths,
dependencies,
setProps,
}){
constthisComponentSharesState=
dependencies&&
dependencies.find(
dependency=>
dependency.inputs.find(input=>input.id===id)||
dependency.state.find(state=>state.id===id)
);
/*
* Only pass in `setProps` if necessary.
* This allows component authors to skip computing unneeded data
* for `setProps`, which can be expensive.
* For example, consider `hoverData` for graphs. If it isn't
* actually used, then the component author can skip binding
* the events for the component.
*
* TODO - A nice enhancement would be to pass in the actual
* properties that are used into the component so that the
* component author can check for something like
* `subscribed_properties` instead of just `setProps`.
*/
constextraProps={};
if(
thisComponentSharesState&&
// there is a bug with graphs right now where
// the restyle listener gets assigned with a
// setProps function that was created before
// the item was added. only pass in setProps
// if the item's path exists for now.
paths[id]
){
extraProps.setProps=setProps;
}
if(!isEmpty(extraProps)){
returnReact.cloneElement(children,extraProps);
}
returnchildren;
}
NotifyObserversComponent.propTypes={
id: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
path: PropTypes.array.isRequired,
};
exportdefaultconnect(
mapStateToProps,
mapDispatchToProps,
mergeProps
)(NotifyObserversComponent);

@Marc-Andre-Rivet

Copy link
Copy Markdown
Contributor

@chriddyp No, your understanding seems to be correct. Adding the prop from the store does not have the impact I thought it would have. My thinking was that since N.Obs. had a new prop from the store, that would trigger extra renders but that assumption was incorrect. There are a few extra renders in the observer when first starting the app but after that it's a one to one match with previous behavior.

Thanks for the input.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

Released as prerelease version 0.18.0rc1.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

There seems to be an issue here that's causing the test_confirm_as_children in dash-core-components to fail. Haven't been able to figure this out yet.

@valentijnnieman

valentijnnieman commented Jan 31, 2019

Copy link
Copy Markdown
ContributorAuthor

Published 0.18.0rc3 which should fix some of the tests in DCC failing.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

Released as prerelease version 0.18.0rc4

Comment threadsimple.py Outdated
Comment threadCHANGELOG.md

## [0.18.0] - 2019-01-30
### Added
- Loading states API [#267](https://github.com/plotly/dash/issues/267)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For final merge, needs to be unreleased and version bump reverted in package.json

Comment threadsrc/TreeContainer.js Outdated
Comment threadsrc/TreeContainer.js Outdated
return nextProps.layout !== this.props.layout;
return (
nextProps.layout !== this.props.layout
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is checking only for the layout enough to update correctly in all cases? How does this cover the requestQueue updates? Or how is the requestQueue not necessary?

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.

I'm not sure about this. The main reason that I haven't added any nextProps.requestQueue checks is that it causes tests to fail. I'm guessing that re-rendering upon differences in the requestQueue prop causes some behaviour to be different - I'm seeing test_radio_buttons_callbacks_generating_children and test_hot_reload tests failing.

Comment threadsrc/TreeContainer.js
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.

6 participants

@valentijnnieman@chriddyp@rmarren1@T4rk1n@Marc-Andre-Rivet@nicolaskruchten
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
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;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} 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.

Loading states api - #93

Merged
valentijnnieman merged 48 commits into
masterfrom
loading_states_api
Feb 28, 2019
Merged

Loading states api#93
valentijnnieman merged 48 commits into
masterfrom
loading_states_api

Conversation

@valentijnnieman

@valentijnniemanvalentijnnieman commented Oct 30, 2018

Copy link
Copy Markdown
Contributor

Here's the first pass at a loading states api, where dash-renderer now provides a loading_state prop to components that are taking some time to load. It is used with a dist of this PR that provides a Loading component, that can wrap any other Dash component, making it display a loading spinner if it's not fully rendered. An example is included in simple.py, make sure to install the dev-requirements.txt before running it!

TODO:

  • Include which property is loading. Currently we strip the component's name, but it also includes the property that's loading, so we could pass that down as well.
  • Clean-up!

The loading_state prop is an object that looks like:

{
is_loading: bool,
component_name: string,
prop_name: string
}

So that component authors can determine when to show a loading spinner, and know which component in the chain caused the loading to happen (it will return the id of the component), and which prop is loading (for example children).

Community post: https://community.plot.ly/t/loading-states-api-and-a-loading-component-prerelease

Comment threadsrc/APIController.react.js Outdated
@valentijnniemanvalentijnnieman changed the title [WIP] Loading states apiLoading states apiNov 6, 2018
@chriddyp

Copy link
Copy Markdown
Member

For those following along in the community, here's a demo:
image
loading states

if (r.status === 'loading' && contains(id, r.controllerId)) {
isLoading = true;
loadingComponent = r.controllerId.split('.')[0];
loadingProp = r.controllerId.split('.')[1];

@T4rk1nT4rk1nNov 26, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can split only one time: [loadingProp, loadingComponent] = r.controllerId.split('.').

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Had a hard time figuring this out, that is not a proper map. It should be a for loop or a filter or a find.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the most appropriate would be a .forEach()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We have been using rambda elsewhere in dash-renderer even when a core javascript function exists for a task, so https://ramdajs.com/docs/#forEach would fit better

}
});

const thisRequest = requestQueue.filter(r => contains(id, r.controllerId));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The filter is here.

Comment threadpackage.json Outdated
@Marc-Andre-Rivet

Marc-Andre-Rivet commented Jan 8, 2019

Copy link
Copy Markdown
Contributor

Would like to see the following issues opened as follow ups:

  • enabling direct support of loading state in DCC components (loading_state + animation type)
  • integration testing support for dash-renderer (our tests are all at the behavioural / dash server level atm) -- test loading states, test loading states transition, test more complex graph shapes (Loading component with multiple children, Loading comp within a parent loading comp)
  • removing the 'Loading...' from dash.py

@Marc-Andre-Rivet

Marc-Andre-Rivet commented Jan 24, 2019

Copy link
Copy Markdown
Contributor

As discussed with @valentijnnieman, we are currently going for a mixed approach, supporting both

  • loading state as a data-attribute on the components + styleguide / css styling of loading comps
  • Loading component with 1 level deep loading_state support -- the user becomes responsible for putting items that are actually slow to load (e.g. no listening to children prop in Dash)

While the updated implementation looks promising, my main concern right now has to do with the mechanics of getting this to work -- the TreeContainer or the NotifyObserver needs to be hooked up to the store's requestQueue through a prop in order to determine the component's state -- the queue is impacted by user actions and changes often during back-and-forth with Dash. Since not all components are pure in html and dcc, or protected by a componentShouldUpdate method, this may cause a significant performance impact. This should be studied and minimized before the feature can be released / sent back for community testing. It's possible that the impact will be negligible but if not, we may have to expose the queue data differently so as to not trigger useless re-render.

@chriddyp

Copy link
Copy Markdown
Member

the TreeContainer or the NotifyObserver needs to be hooked up to the store's requestQueue through a prop in order to determine the component's state

I'm a little rusty on the redux details here, but I thought that any dispatch that happens will trigger an update. I don't believe that there isn't anything in NotifyObservers that says "If store.x changes, then update. But if store.y changes, don't update.". Does this match your understanding too or am I forgetting about something? If so, can you point me to the code where this happens?

So, if my understanding is right, the existing code is dispatching to update the request queue which is already cause re-rendering. Passing new props through in NotifyObserversComponent shouldn't change this.

Code reference: dispatch(setRequestQueue(

dispatch(setRequestQueue(concat(requestQueue,newRequestQueue)));

Connected NotifyObservers

/*
* NotifyObservers passes a connected `setProps` handler down to
* its child as a prop
*/
functionmapStateToProps(state){
return{
dependencies: state.dependenciesRequest.content,
paths: state.paths,
};
}
functionmapDispatchToProps(dispatch){
return{dispatch};
}
functionmergeProps(stateProps,dispatchProps,ownProps){
const{dispatch}=dispatchProps;
return{
id: ownProps.id,
children: ownProps.children,
dependencies: stateProps.dependencies,
paths: stateProps.paths,
setProps: functionsetProps(newProps){
constpayload={
props: newProps,
id: ownProps.id,
itempath: stateProps.paths[ownProps.id],
};
// Update this component's props
dispatch(updateProps(payload));
// Update output components that depend on this input
dispatch(notifyObservers({id: ownProps.id,props: newProps}));
},
};
}
functionNotifyObserversComponent({
children,
id,
paths,
dependencies,
setProps,
}){
constthisComponentSharesState=
dependencies&&
dependencies.find(
dependency=>
dependency.inputs.find(input=>input.id===id)||
dependency.state.find(state=>state.id===id)
);
/*
* Only pass in `setProps` if necessary.
* This allows component authors to skip computing unneeded data
* for `setProps`, which can be expensive.
* For example, consider `hoverData` for graphs. If it isn't
* actually used, then the component author can skip binding
* the events for the component.
*
* TODO - A nice enhancement would be to pass in the actual
* properties that are used into the component so that the
* component author can check for something like
* `subscribed_properties` instead of just `setProps`.
*/
constextraProps={};
if(
thisComponentSharesState&&
// there is a bug with graphs right now where
// the restyle listener gets assigned with a
// setProps function that was created before
// the item was added. only pass in setProps
// if the item's path exists for now.
paths[id]
){
extraProps.setProps=setProps;
}
if(!isEmpty(extraProps)){
returnReact.cloneElement(children,extraProps);
}
returnchildren;
}
NotifyObserversComponent.propTypes={
id: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
path: PropTypes.array.isRequired,
};
exportdefaultconnect(
mapStateToProps,
mapDispatchToProps,
mergeProps
)(NotifyObserversComponent);

@Marc-Andre-Rivet

Copy link
Copy Markdown
Contributor

@chriddyp No, your understanding seems to be correct. Adding the prop from the store does not have the impact I thought it would have. My thinking was that since N.Obs. had a new prop from the store, that would trigger extra renders but that assumption was incorrect. There are a few extra renders in the observer when first starting the app but after that it's a one to one match with previous behavior.

Thanks for the input.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

Released as prerelease version 0.18.0rc1.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

There seems to be an issue here that's causing the test_confirm_as_children in dash-core-components to fail. Haven't been able to figure this out yet.

@valentijnnieman

valentijnnieman commented Jan 31, 2019

Copy link
Copy Markdown
ContributorAuthor

Published 0.18.0rc3 which should fix some of the tests in DCC failing.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

Released as prerelease version 0.18.0rc4

Comment threadsimple.py Outdated
Comment threadCHANGELOG.md

## [0.18.0] - 2019-01-30
### Added
- Loading states API [#267](https://github.com/plotly/dash/issues/267)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For final merge, needs to be unreleased and version bump reverted in package.json

Comment threadsrc/TreeContainer.js Outdated
Comment threadsrc/TreeContainer.js Outdated
return nextProps.layout !== this.props.layout;
return (
nextProps.layout !== this.props.layout
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is checking only for the layout enough to update correctly in all cases? How does this cover the requestQueue updates? Or how is the requestQueue not necessary?

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.

I'm not sure about this. The main reason that I haven't added any nextProps.requestQueue checks is that it causes tests to fail. I'm guessing that re-rendering upon differences in the requestQueue prop causes some behaviour to be different - I'm seeing test_radio_buttons_callbacks_generating_children and test_hot_reload tests failing.

Comment threadsrc/TreeContainer.js
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.

6 participants

@valentijnnieman@chriddyp@rmarren1@T4rk1n@Marc-Andre-Rivet@nicolaskruchten
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } 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.

Loading states api - #93

Merged
valentijnnieman merged 48 commits into
masterfrom
loading_states_api
Feb 28, 2019
Merged

Loading states api#93
valentijnnieman merged 48 commits into
masterfrom
loading_states_api

Conversation

@valentijnnieman

@valentijnniemanvalentijnnieman commented Oct 30, 2018

Copy link
Copy Markdown
Contributor

Here's the first pass at a loading states api, where dash-renderer now provides a loading_state prop to components that are taking some time to load. It is used with a dist of this PR that provides a Loading component, that can wrap any other Dash component, making it display a loading spinner if it's not fully rendered. An example is included in simple.py, make sure to install the dev-requirements.txt before running it!

TODO:

  • Include which property is loading. Currently we strip the component's name, but it also includes the property that's loading, so we could pass that down as well.
  • Clean-up!

The loading_state prop is an object that looks like:

{
is_loading: bool,
component_name: string,
prop_name: string
}

So that component authors can determine when to show a loading spinner, and know which component in the chain caused the loading to happen (it will return the id of the component), and which prop is loading (for example children).

Community post: https://community.plot.ly/t/loading-states-api-and-a-loading-component-prerelease

Comment threadsrc/APIController.react.js Outdated
@valentijnniemanvalentijnnieman changed the title [WIP] Loading states apiLoading states apiNov 6, 2018
@chriddyp

Copy link
Copy Markdown
Member

For those following along in the community, here's a demo:
image
loading states

if (r.status === 'loading' && contains(id, r.controllerId)) {
isLoading = true;
loadingComponent = r.controllerId.split('.')[0];
loadingProp = r.controllerId.split('.')[1];

@T4rk1nT4rk1nNov 26, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can split only one time: [loadingProp, loadingComponent] = r.controllerId.split('.').

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Had a hard time figuring this out, that is not a proper map. It should be a for loop or a filter or a find.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the most appropriate would be a .forEach()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We have been using rambda elsewhere in dash-renderer even when a core javascript function exists for a task, so https://ramdajs.com/docs/#forEach would fit better

}
});

const thisRequest = requestQueue.filter(r => contains(id, r.controllerId));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The filter is here.

Comment threadpackage.json Outdated
@Marc-Andre-Rivet

Marc-Andre-Rivet commented Jan 8, 2019

Copy link
Copy Markdown
Contributor

Would like to see the following issues opened as follow ups:

  • enabling direct support of loading state in DCC components (loading_state + animation type)
  • integration testing support for dash-renderer (our tests are all at the behavioural / dash server level atm) -- test loading states, test loading states transition, test more complex graph shapes (Loading component with multiple children, Loading comp within a parent loading comp)
  • removing the 'Loading...' from dash.py

@Marc-Andre-Rivet

Marc-Andre-Rivet commented Jan 24, 2019

Copy link
Copy Markdown
Contributor

As discussed with @valentijnnieman, we are currently going for a mixed approach, supporting both

  • loading state as a data-attribute on the components + styleguide / css styling of loading comps
  • Loading component with 1 level deep loading_state support -- the user becomes responsible for putting items that are actually slow to load (e.g. no listening to children prop in Dash)

While the updated implementation looks promising, my main concern right now has to do with the mechanics of getting this to work -- the TreeContainer or the NotifyObserver needs to be hooked up to the store's requestQueue through a prop in order to determine the component's state -- the queue is impacted by user actions and changes often during back-and-forth with Dash. Since not all components are pure in html and dcc, or protected by a componentShouldUpdate method, this may cause a significant performance impact. This should be studied and minimized before the feature can be released / sent back for community testing. It's possible that the impact will be negligible but if not, we may have to expose the queue data differently so as to not trigger useless re-render.

@chriddyp

Copy link
Copy Markdown
Member

the TreeContainer or the NotifyObserver needs to be hooked up to the store's requestQueue through a prop in order to determine the component's state

I'm a little rusty on the redux details here, but I thought that any dispatch that happens will trigger an update. I don't believe that there isn't anything in NotifyObservers that says "If store.x changes, then update. But if store.y changes, don't update.". Does this match your understanding too or am I forgetting about something? If so, can you point me to the code where this happens?

So, if my understanding is right, the existing code is dispatching to update the request queue which is already cause re-rendering. Passing new props through in NotifyObserversComponent shouldn't change this.

Code reference: dispatch(setRequestQueue(

dispatch(setRequestQueue(concat(requestQueue,newRequestQueue)));

Connected NotifyObservers

/*
* NotifyObservers passes a connected `setProps` handler down to
* its child as a prop
*/
functionmapStateToProps(state){
return{
dependencies: state.dependenciesRequest.content,
paths: state.paths,
};
}
functionmapDispatchToProps(dispatch){
return{dispatch};
}
functionmergeProps(stateProps,dispatchProps,ownProps){
const{dispatch}=dispatchProps;
return{
id: ownProps.id,
children: ownProps.children,
dependencies: stateProps.dependencies,
paths: stateProps.paths,
setProps: functionsetProps(newProps){
constpayload={
props: newProps,
id: ownProps.id,
itempath: stateProps.paths[ownProps.id],
};
// Update this component's props
dispatch(updateProps(payload));
// Update output components that depend on this input
dispatch(notifyObservers({id: ownProps.id,props: newProps}));
},
};
}
functionNotifyObserversComponent({
children,
id,
paths,
dependencies,
setProps,
}){
constthisComponentSharesState=
dependencies&&
dependencies.find(
dependency=>
dependency.inputs.find(input=>input.id===id)||
dependency.state.find(state=>state.id===id)
);
/*
* Only pass in `setProps` if necessary.
* This allows component authors to skip computing unneeded data
* for `setProps`, which can be expensive.
* For example, consider `hoverData` for graphs. If it isn't
* actually used, then the component author can skip binding
* the events for the component.
*
* TODO - A nice enhancement would be to pass in the actual
* properties that are used into the component so that the
* component author can check for something like
* `subscribed_properties` instead of just `setProps`.
*/
constextraProps={};
if(
thisComponentSharesState&&
// there is a bug with graphs right now where
// the restyle listener gets assigned with a
// setProps function that was created before
// the item was added. only pass in setProps
// if the item's path exists for now.
paths[id]
){
extraProps.setProps=setProps;
}
if(!isEmpty(extraProps)){
returnReact.cloneElement(children,extraProps);
}
returnchildren;
}
NotifyObserversComponent.propTypes={
id: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
path: PropTypes.array.isRequired,
};
exportdefaultconnect(
mapStateToProps,
mapDispatchToProps,
mergeProps
)(NotifyObserversComponent);

@Marc-Andre-Rivet

Copy link
Copy Markdown
Contributor

@chriddyp No, your understanding seems to be correct. Adding the prop from the store does not have the impact I thought it would have. My thinking was that since N.Obs. had a new prop from the store, that would trigger extra renders but that assumption was incorrect. There are a few extra renders in the observer when first starting the app but after that it's a one to one match with previous behavior.

Thanks for the input.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

Released as prerelease version 0.18.0rc1.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

There seems to be an issue here that's causing the test_confirm_as_children in dash-core-components to fail. Haven't been able to figure this out yet.

@valentijnnieman

valentijnnieman commented Jan 31, 2019

Copy link
Copy Markdown
ContributorAuthor

Published 0.18.0rc3 which should fix some of the tests in DCC failing.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

Released as prerelease version 0.18.0rc4

Comment threadsimple.py Outdated
Comment threadCHANGELOG.md

## [0.18.0] - 2019-01-30
### Added
- Loading states API [#267](https://github.com/plotly/dash/issues/267)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For final merge, needs to be unreleased and version bump reverted in package.json

Comment threadsrc/TreeContainer.js Outdated
Comment threadsrc/TreeContainer.js Outdated
return nextProps.layout !== this.props.layout;
return (
nextProps.layout !== this.props.layout
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is checking only for the layout enough to update correctly in all cases? How does this cover the requestQueue updates? Or how is the requestQueue not necessary?

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.

I'm not sure about this. The main reason that I haven't added any nextProps.requestQueue checks is that it causes tests to fail. I'm guessing that re-rendering upon differences in the requestQueue prop causes some behaviour to be different - I'm seeing test_radio_buttons_callbacks_generating_children and test_hot_reload tests failing.

Comment threadsrc/TreeContainer.js
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.

6 participants

@valentijnnieman@chriddyp@rmarren1@T4rk1n@Marc-Andre-Rivet@nicolaskruchten
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } 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.

Loading states api - #93

Merged
valentijnnieman merged 48 commits into
masterfrom
loading_states_api
Feb 28, 2019
Merged

Loading states api#93
valentijnnieman merged 48 commits into
masterfrom
loading_states_api

Conversation

@valentijnnieman

@valentijnniemanvalentijnnieman commented Oct 30, 2018

Copy link
Copy Markdown
Contributor

Here's the first pass at a loading states api, where dash-renderer now provides a loading_state prop to components that are taking some time to load. It is used with a dist of this PR that provides a Loading component, that can wrap any other Dash component, making it display a loading spinner if it's not fully rendered. An example is included in simple.py, make sure to install the dev-requirements.txt before running it!

TODO:

  • Include which property is loading. Currently we strip the component's name, but it also includes the property that's loading, so we could pass that down as well.
  • Clean-up!

The loading_state prop is an object that looks like:

{
is_loading: bool,
component_name: string,
prop_name: string
}

So that component authors can determine when to show a loading spinner, and know which component in the chain caused the loading to happen (it will return the id of the component), and which prop is loading (for example children).

Community post: https://community.plot.ly/t/loading-states-api-and-a-loading-component-prerelease

Comment threadsrc/APIController.react.js Outdated
@valentijnniemanvalentijnnieman changed the title [WIP] Loading states apiLoading states apiNov 6, 2018
@chriddyp

Copy link
Copy Markdown
Member

For those following along in the community, here's a demo:
image
loading states

if (r.status === 'loading' && contains(id, r.controllerId)) {
isLoading = true;
loadingComponent = r.controllerId.split('.')[0];
loadingProp = r.controllerId.split('.')[1];

@T4rk1nT4rk1nNov 26, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can split only one time: [loadingProp, loadingComponent] = r.controllerId.split('.').

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Had a hard time figuring this out, that is not a proper map. It should be a for loop or a filter or a find.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the most appropriate would be a .forEach()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We have been using rambda elsewhere in dash-renderer even when a core javascript function exists for a task, so https://ramdajs.com/docs/#forEach would fit better

}
});

const thisRequest = requestQueue.filter(r => contains(id, r.controllerId));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The filter is here.

Comment threadpackage.json Outdated
@Marc-Andre-Rivet

Marc-Andre-Rivet commented Jan 8, 2019

Copy link
Copy Markdown
Contributor

Would like to see the following issues opened as follow ups:

  • enabling direct support of loading state in DCC components (loading_state + animation type)
  • integration testing support for dash-renderer (our tests are all at the behavioural / dash server level atm) -- test loading states, test loading states transition, test more complex graph shapes (Loading component with multiple children, Loading comp within a parent loading comp)
  • removing the 'Loading...' from dash.py

@Marc-Andre-Rivet

Marc-Andre-Rivet commented Jan 24, 2019

Copy link
Copy Markdown
Contributor

As discussed with @valentijnnieman, we are currently going for a mixed approach, supporting both

  • loading state as a data-attribute on the components + styleguide / css styling of loading comps
  • Loading component with 1 level deep loading_state support -- the user becomes responsible for putting items that are actually slow to load (e.g. no listening to children prop in Dash)

While the updated implementation looks promising, my main concern right now has to do with the mechanics of getting this to work -- the TreeContainer or the NotifyObserver needs to be hooked up to the store's requestQueue through a prop in order to determine the component's state -- the queue is impacted by user actions and changes often during back-and-forth with Dash. Since not all components are pure in html and dcc, or protected by a componentShouldUpdate method, this may cause a significant performance impact. This should be studied and minimized before the feature can be released / sent back for community testing. It's possible that the impact will be negligible but if not, we may have to expose the queue data differently so as to not trigger useless re-render.

@chriddyp

Copy link
Copy Markdown
Member

the TreeContainer or the NotifyObserver needs to be hooked up to the store's requestQueue through a prop in order to determine the component's state

I'm a little rusty on the redux details here, but I thought that any dispatch that happens will trigger an update. I don't believe that there isn't anything in NotifyObservers that says "If store.x changes, then update. But if store.y changes, don't update.". Does this match your understanding too or am I forgetting about something? If so, can you point me to the code where this happens?

So, if my understanding is right, the existing code is dispatching to update the request queue which is already cause re-rendering. Passing new props through in NotifyObserversComponent shouldn't change this.

Code reference: dispatch(setRequestQueue(

dispatch(setRequestQueue(concat(requestQueue,newRequestQueue)));

Connected NotifyObservers

/*
* NotifyObservers passes a connected `setProps` handler down to
* its child as a prop
*/
functionmapStateToProps(state){
return{
dependencies: state.dependenciesRequest.content,
paths: state.paths,
};
}
functionmapDispatchToProps(dispatch){
return{dispatch};
}
functionmergeProps(stateProps,dispatchProps,ownProps){
const{dispatch}=dispatchProps;
return{
id: ownProps.id,
children: ownProps.children,
dependencies: stateProps.dependencies,
paths: stateProps.paths,
setProps: functionsetProps(newProps){
constpayload={
props: newProps,
id: ownProps.id,
itempath: stateProps.paths[ownProps.id],
};
// Update this component's props
dispatch(updateProps(payload));
// Update output components that depend on this input
dispatch(notifyObservers({id: ownProps.id,props: newProps}));
},
};
}
functionNotifyObserversComponent({
children,
id,
paths,
dependencies,
setProps,
}){
constthisComponentSharesState=
dependencies&&
dependencies.find(
dependency=>
dependency.inputs.find(input=>input.id===id)||
dependency.state.find(state=>state.id===id)
);
/*
* Only pass in `setProps` if necessary.
* This allows component authors to skip computing unneeded data
* for `setProps`, which can be expensive.
* For example, consider `hoverData` for graphs. If it isn't
* actually used, then the component author can skip binding
* the events for the component.
*
* TODO - A nice enhancement would be to pass in the actual
* properties that are used into the component so that the
* component author can check for something like
* `subscribed_properties` instead of just `setProps`.
*/
constextraProps={};
if(
thisComponentSharesState&&
// there is a bug with graphs right now where
// the restyle listener gets assigned with a
// setProps function that was created before
// the item was added. only pass in setProps
// if the item's path exists for now.
paths[id]
){
extraProps.setProps=setProps;
}
if(!isEmpty(extraProps)){
returnReact.cloneElement(children,extraProps);
}
returnchildren;
}
NotifyObserversComponent.propTypes={
id: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
path: PropTypes.array.isRequired,
};
exportdefaultconnect(
mapStateToProps,
mapDispatchToProps,
mergeProps
)(NotifyObserversComponent);

@Marc-Andre-Rivet

Copy link
Copy Markdown
Contributor

@chriddyp No, your understanding seems to be correct. Adding the prop from the store does not have the impact I thought it would have. My thinking was that since N.Obs. had a new prop from the store, that would trigger extra renders but that assumption was incorrect. There are a few extra renders in the observer when first starting the app but after that it's a one to one match with previous behavior.

Thanks for the input.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

Released as prerelease version 0.18.0rc1.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

There seems to be an issue here that's causing the test_confirm_as_children in dash-core-components to fail. Haven't been able to figure this out yet.

@valentijnnieman

valentijnnieman commented Jan 31, 2019

Copy link
Copy Markdown
ContributorAuthor

Published 0.18.0rc3 which should fix some of the tests in DCC failing.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

Released as prerelease version 0.18.0rc4

Comment threadsimple.py Outdated
Comment threadCHANGELOG.md

## [0.18.0] - 2019-01-30
### Added
- Loading states API [#267](https://github.com/plotly/dash/issues/267)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For final merge, needs to be unreleased and version bump reverted in package.json

Comment threadsrc/TreeContainer.js Outdated
Comment threadsrc/TreeContainer.js Outdated
return nextProps.layout !== this.props.layout;
return (
nextProps.layout !== this.props.layout
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is checking only for the layout enough to update correctly in all cases? How does this cover the requestQueue updates? Or how is the requestQueue not necessary?

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.

I'm not sure about this. The main reason that I haven't added any nextProps.requestQueue checks is that it causes tests to fail. I'm guessing that re-rendering upon differences in the requestQueue prop causes some behaviour to be different - I'm seeing test_radio_buttons_callbacks_generating_children and test_hot_reload tests failing.

Comment threadsrc/TreeContainer.js
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.

6 participants

@valentijnnieman@chriddyp@rmarren1@T4rk1n@Marc-Andre-Rivet@nicolaskruchten
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } 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.

Loading states api - #93

Merged
valentijnnieman merged 48 commits into
masterfrom
loading_states_api
Feb 28, 2019
Merged

Loading states api#93
valentijnnieman merged 48 commits into
masterfrom
loading_states_api

Conversation

@valentijnnieman

@valentijnniemanvalentijnnieman commented Oct 30, 2018

Copy link
Copy Markdown
Contributor

Here's the first pass at a loading states api, where dash-renderer now provides a loading_state prop to components that are taking some time to load. It is used with a dist of this PR that provides a Loading component, that can wrap any other Dash component, making it display a loading spinner if it's not fully rendered. An example is included in simple.py, make sure to install the dev-requirements.txt before running it!

TODO:

  • Include which property is loading. Currently we strip the component's name, but it also includes the property that's loading, so we could pass that down as well.
  • Clean-up!

The loading_state prop is an object that looks like:

{
is_loading: bool,
component_name: string,
prop_name: string
}

So that component authors can determine when to show a loading spinner, and know which component in the chain caused the loading to happen (it will return the id of the component), and which prop is loading (for example children).

Community post: https://community.plot.ly/t/loading-states-api-and-a-loading-component-prerelease

Comment threadsrc/APIController.react.js Outdated
@valentijnniemanvalentijnnieman changed the title [WIP] Loading states apiLoading states apiNov 6, 2018
@chriddyp

Copy link
Copy Markdown
Member

For those following along in the community, here's a demo:
image
loading states

if (r.status === 'loading' && contains(id, r.controllerId)) {
isLoading = true;
loadingComponent = r.controllerId.split('.')[0];
loadingProp = r.controllerId.split('.')[1];

@T4rk1nT4rk1nNov 26, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can split only one time: [loadingProp, loadingComponent] = r.controllerId.split('.').

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Had a hard time figuring this out, that is not a proper map. It should be a for loop or a filter or a find.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the most appropriate would be a .forEach()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We have been using rambda elsewhere in dash-renderer even when a core javascript function exists for a task, so https://ramdajs.com/docs/#forEach would fit better

}
});

const thisRequest = requestQueue.filter(r => contains(id, r.controllerId));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The filter is here.

Comment threadpackage.json Outdated
@Marc-Andre-Rivet

Marc-Andre-Rivet commented Jan 8, 2019

Copy link
Copy Markdown
Contributor

Would like to see the following issues opened as follow ups:

  • enabling direct support of loading state in DCC components (loading_state + animation type)
  • integration testing support for dash-renderer (our tests are all at the behavioural / dash server level atm) -- test loading states, test loading states transition, test more complex graph shapes (Loading component with multiple children, Loading comp within a parent loading comp)
  • removing the 'Loading...' from dash.py

@Marc-Andre-Rivet

Marc-Andre-Rivet commented Jan 24, 2019

Copy link
Copy Markdown
Contributor

As discussed with @valentijnnieman, we are currently going for a mixed approach, supporting both

  • loading state as a data-attribute on the components + styleguide / css styling of loading comps
  • Loading component with 1 level deep loading_state support -- the user becomes responsible for putting items that are actually slow to load (e.g. no listening to children prop in Dash)

While the updated implementation looks promising, my main concern right now has to do with the mechanics of getting this to work -- the TreeContainer or the NotifyObserver needs to be hooked up to the store's requestQueue through a prop in order to determine the component's state -- the queue is impacted by user actions and changes often during back-and-forth with Dash. Since not all components are pure in html and dcc, or protected by a componentShouldUpdate method, this may cause a significant performance impact. This should be studied and minimized before the feature can be released / sent back for community testing. It's possible that the impact will be negligible but if not, we may have to expose the queue data differently so as to not trigger useless re-render.

@chriddyp

Copy link
Copy Markdown
Member

the TreeContainer or the NotifyObserver needs to be hooked up to the store's requestQueue through a prop in order to determine the component's state

I'm a little rusty on the redux details here, but I thought that any dispatch that happens will trigger an update. I don't believe that there isn't anything in NotifyObservers that says "If store.x changes, then update. But if store.y changes, don't update.". Does this match your understanding too or am I forgetting about something? If so, can you point me to the code where this happens?

So, if my understanding is right, the existing code is dispatching to update the request queue which is already cause re-rendering. Passing new props through in NotifyObserversComponent shouldn't change this.

Code reference: dispatch(setRequestQueue(

dispatch(setRequestQueue(concat(requestQueue,newRequestQueue)));

Connected NotifyObservers

/*
* NotifyObservers passes a connected `setProps` handler down to
* its child as a prop
*/
functionmapStateToProps(state){
return{
dependencies: state.dependenciesRequest.content,
paths: state.paths,
};
}
functionmapDispatchToProps(dispatch){
return{dispatch};
}
functionmergeProps(stateProps,dispatchProps,ownProps){
const{dispatch}=dispatchProps;
return{
id: ownProps.id,
children: ownProps.children,
dependencies: stateProps.dependencies,
paths: stateProps.paths,
setProps: functionsetProps(newProps){
constpayload={
props: newProps,
id: ownProps.id,
itempath: stateProps.paths[ownProps.id],
};
// Update this component's props
dispatch(updateProps(payload));
// Update output components that depend on this input
dispatch(notifyObservers({id: ownProps.id,props: newProps}));
},
};
}
functionNotifyObserversComponent({
children,
id,
paths,
dependencies,
setProps,
}){
constthisComponentSharesState=
dependencies&&
dependencies.find(
dependency=>
dependency.inputs.find(input=>input.id===id)||
dependency.state.find(state=>state.id===id)
);
/*
* Only pass in `setProps` if necessary.
* This allows component authors to skip computing unneeded data
* for `setProps`, which can be expensive.
* For example, consider `hoverData` for graphs. If it isn't
* actually used, then the component author can skip binding
* the events for the component.
*
* TODO - A nice enhancement would be to pass in the actual
* properties that are used into the component so that the
* component author can check for something like
* `subscribed_properties` instead of just `setProps`.
*/
constextraProps={};
if(
thisComponentSharesState&&
// there is a bug with graphs right now where
// the restyle listener gets assigned with a
// setProps function that was created before
// the item was added. only pass in setProps
// if the item's path exists for now.
paths[id]
){
extraProps.setProps=setProps;
}
if(!isEmpty(extraProps)){
returnReact.cloneElement(children,extraProps);
}
returnchildren;
}
NotifyObserversComponent.propTypes={
id: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
path: PropTypes.array.isRequired,
};
exportdefaultconnect(
mapStateToProps,
mapDispatchToProps,
mergeProps
)(NotifyObserversComponent);

@Marc-Andre-Rivet

Copy link
Copy Markdown
Contributor

@chriddyp No, your understanding seems to be correct. Adding the prop from the store does not have the impact I thought it would have. My thinking was that since N.Obs. had a new prop from the store, that would trigger extra renders but that assumption was incorrect. There are a few extra renders in the observer when first starting the app but after that it's a one to one match with previous behavior.

Thanks for the input.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

Released as prerelease version 0.18.0rc1.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

There seems to be an issue here that's causing the test_confirm_as_children in dash-core-components to fail. Haven't been able to figure this out yet.

@valentijnnieman

valentijnnieman commented Jan 31, 2019

Copy link
Copy Markdown
ContributorAuthor

Published 0.18.0rc3 which should fix some of the tests in DCC failing.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

Released as prerelease version 0.18.0rc4

Comment threadsimple.py Outdated
Comment threadCHANGELOG.md

## [0.18.0] - 2019-01-30
### Added
- Loading states API [#267](https://github.com/plotly/dash/issues/267)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For final merge, needs to be unreleased and version bump reverted in package.json

Comment threadsrc/TreeContainer.js Outdated
Comment threadsrc/TreeContainer.js Outdated
return nextProps.layout !== this.props.layout;
return (
nextProps.layout !== this.props.layout
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is checking only for the layout enough to update correctly in all cases? How does this cover the requestQueue updates? Or how is the requestQueue not necessary?

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.

I'm not sure about this. The main reason that I haven't added any nextProps.requestQueue checks is that it causes tests to fail. I'm guessing that re-rendering upon differences in the requestQueue prop causes some behaviour to be different - I'm seeing test_radio_buttons_callbacks_generating_children and test_hot_reload tests failing.

Comment threadsrc/TreeContainer.js
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.

6 participants

@valentijnnieman@chriddyp@rmarren1@T4rk1n@Marc-Andre-Rivet@nicolaskruchten
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } 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.

Loading states api - #93

Merged
valentijnnieman merged 48 commits into
masterfrom
loading_states_api
Feb 28, 2019
Merged

Loading states api#93
valentijnnieman merged 48 commits into
masterfrom
loading_states_api

Conversation

@valentijnnieman

@valentijnniemanvalentijnnieman commented Oct 30, 2018

Copy link
Copy Markdown
Contributor

Here's the first pass at a loading states api, where dash-renderer now provides a loading_state prop to components that are taking some time to load. It is used with a dist of this PR that provides a Loading component, that can wrap any other Dash component, making it display a loading spinner if it's not fully rendered. An example is included in simple.py, make sure to install the dev-requirements.txt before running it!

TODO:

  • Include which property is loading. Currently we strip the component's name, but it also includes the property that's loading, so we could pass that down as well.
  • Clean-up!

The loading_state prop is an object that looks like:

{
is_loading: bool,
component_name: string,
prop_name: string
}

So that component authors can determine when to show a loading spinner, and know which component in the chain caused the loading to happen (it will return the id of the component), and which prop is loading (for example children).

Community post: https://community.plot.ly/t/loading-states-api-and-a-loading-component-prerelease

Comment threadsrc/APIController.react.js Outdated
@valentijnniemanvalentijnnieman changed the title [WIP] Loading states apiLoading states apiNov 6, 2018
@chriddyp

Copy link
Copy Markdown
Member

For those following along in the community, here's a demo:
image
loading states

if (r.status === 'loading' && contains(id, r.controllerId)) {
isLoading = true;
loadingComponent = r.controllerId.split('.')[0];
loadingProp = r.controllerId.split('.')[1];

@T4rk1nT4rk1nNov 26, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can split only one time: [loadingProp, loadingComponent] = r.controllerId.split('.').

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Had a hard time figuring this out, that is not a proper map. It should be a for loop or a filter or a find.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the most appropriate would be a .forEach()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We have been using rambda elsewhere in dash-renderer even when a core javascript function exists for a task, so https://ramdajs.com/docs/#forEach would fit better

}
});

const thisRequest = requestQueue.filter(r => contains(id, r.controllerId));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The filter is here.

Comment threadpackage.json Outdated
@Marc-Andre-Rivet

Marc-Andre-Rivet commented Jan 8, 2019

Copy link
Copy Markdown
Contributor

Would like to see the following issues opened as follow ups:

  • enabling direct support of loading state in DCC components (loading_state + animation type)
  • integration testing support for dash-renderer (our tests are all at the behavioural / dash server level atm) -- test loading states, test loading states transition, test more complex graph shapes (Loading component with multiple children, Loading comp within a parent loading comp)
  • removing the 'Loading...' from dash.py

@Marc-Andre-Rivet

Marc-Andre-Rivet commented Jan 24, 2019

Copy link
Copy Markdown
Contributor

As discussed with @valentijnnieman, we are currently going for a mixed approach, supporting both

  • loading state as a data-attribute on the components + styleguide / css styling of loading comps
  • Loading component with 1 level deep loading_state support -- the user becomes responsible for putting items that are actually slow to load (e.g. no listening to children prop in Dash)

While the updated implementation looks promising, my main concern right now has to do with the mechanics of getting this to work -- the TreeContainer or the NotifyObserver needs to be hooked up to the store's requestQueue through a prop in order to determine the component's state -- the queue is impacted by user actions and changes often during back-and-forth with Dash. Since not all components are pure in html and dcc, or protected by a componentShouldUpdate method, this may cause a significant performance impact. This should be studied and minimized before the feature can be released / sent back for community testing. It's possible that the impact will be negligible but if not, we may have to expose the queue data differently so as to not trigger useless re-render.

@chriddyp

Copy link
Copy Markdown
Member

the TreeContainer or the NotifyObserver needs to be hooked up to the store's requestQueue through a prop in order to determine the component's state

I'm a little rusty on the redux details here, but I thought that any dispatch that happens will trigger an update. I don't believe that there isn't anything in NotifyObservers that says "If store.x changes, then update. But if store.y changes, don't update.". Does this match your understanding too or am I forgetting about something? If so, can you point me to the code where this happens?

So, if my understanding is right, the existing code is dispatching to update the request queue which is already cause re-rendering. Passing new props through in NotifyObserversComponent shouldn't change this.

Code reference: dispatch(setRequestQueue(

dispatch(setRequestQueue(concat(requestQueue,newRequestQueue)));

Connected NotifyObservers

/*
* NotifyObservers passes a connected `setProps` handler down to
* its child as a prop
*/
functionmapStateToProps(state){
return{
dependencies: state.dependenciesRequest.content,
paths: state.paths,
};
}
functionmapDispatchToProps(dispatch){
return{dispatch};
}
functionmergeProps(stateProps,dispatchProps,ownProps){
const{dispatch}=dispatchProps;
return{
id: ownProps.id,
children: ownProps.children,
dependencies: stateProps.dependencies,
paths: stateProps.paths,
setProps: functionsetProps(newProps){
constpayload={
props: newProps,
id: ownProps.id,
itempath: stateProps.paths[ownProps.id],
};
// Update this component's props
dispatch(updateProps(payload));
// Update output components that depend on this input
dispatch(notifyObservers({id: ownProps.id,props: newProps}));
},
};
}
functionNotifyObserversComponent({
children,
id,
paths,
dependencies,
setProps,
}){
constthisComponentSharesState=
dependencies&&
dependencies.find(
dependency=>
dependency.inputs.find(input=>input.id===id)||
dependency.state.find(state=>state.id===id)
);
/*
* Only pass in `setProps` if necessary.
* This allows component authors to skip computing unneeded data
* for `setProps`, which can be expensive.
* For example, consider `hoverData` for graphs. If it isn't
* actually used, then the component author can skip binding
* the events for the component.
*
* TODO - A nice enhancement would be to pass in the actual
* properties that are used into the component so that the
* component author can check for something like
* `subscribed_properties` instead of just `setProps`.
*/
constextraProps={};
if(
thisComponentSharesState&&
// there is a bug with graphs right now where
// the restyle listener gets assigned with a
// setProps function that was created before
// the item was added. only pass in setProps
// if the item's path exists for now.
paths[id]
){
extraProps.setProps=setProps;
}
if(!isEmpty(extraProps)){
returnReact.cloneElement(children,extraProps);
}
returnchildren;
}
NotifyObserversComponent.propTypes={
id: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
path: PropTypes.array.isRequired,
};
exportdefaultconnect(
mapStateToProps,
mapDispatchToProps,
mergeProps
)(NotifyObserversComponent);

@Marc-Andre-Rivet

Copy link
Copy Markdown
Contributor

@chriddyp No, your understanding seems to be correct. Adding the prop from the store does not have the impact I thought it would have. My thinking was that since N.Obs. had a new prop from the store, that would trigger extra renders but that assumption was incorrect. There are a few extra renders in the observer when first starting the app but after that it's a one to one match with previous behavior.

Thanks for the input.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

Released as prerelease version 0.18.0rc1.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

There seems to be an issue here that's causing the test_confirm_as_children in dash-core-components to fail. Haven't been able to figure this out yet.

@valentijnnieman

valentijnnieman commented Jan 31, 2019

Copy link
Copy Markdown
ContributorAuthor

Published 0.18.0rc3 which should fix some of the tests in DCC failing.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

Released as prerelease version 0.18.0rc4

Comment threadsimple.py Outdated
Comment threadCHANGELOG.md

## [0.18.0] - 2019-01-30
### Added
- Loading states API [#267](https://github.com/plotly/dash/issues/267)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For final merge, needs to be unreleased and version bump reverted in package.json

Comment threadsrc/TreeContainer.js Outdated
Comment threadsrc/TreeContainer.js Outdated
return nextProps.layout !== this.props.layout;
return (
nextProps.layout !== this.props.layout
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is checking only for the layout enough to update correctly in all cases? How does this cover the requestQueue updates? Or how is the requestQueue not necessary?

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.

I'm not sure about this. The main reason that I haven't added any nextProps.requestQueue checks is that it causes tests to fail. I'm guessing that re-rendering upon differences in the requestQueue prop causes some behaviour to be different - I'm seeing test_radio_buttons_callbacks_generating_children and test_hot_reload tests failing.

Comment threadsrc/TreeContainer.js
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.

6 participants

@valentijnnieman@chriddyp@rmarren1@T4rk1n@Marc-Andre-Rivet@nicolaskruchten
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } 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.

Loading states api - #93

Merged
valentijnnieman merged 48 commits into
masterfrom
loading_states_api
Feb 28, 2019
Merged

Loading states api#93
valentijnnieman merged 48 commits into
masterfrom
loading_states_api

Conversation

@valentijnnieman

@valentijnniemanvalentijnnieman commented Oct 30, 2018

Copy link
Copy Markdown
Contributor

Here's the first pass at a loading states api, where dash-renderer now provides a loading_state prop to components that are taking some time to load. It is used with a dist of this PR that provides a Loading component, that can wrap any other Dash component, making it display a loading spinner if it's not fully rendered. An example is included in simple.py, make sure to install the dev-requirements.txt before running it!

TODO:

  • Include which property is loading. Currently we strip the component's name, but it also includes the property that's loading, so we could pass that down as well.
  • Clean-up!

The loading_state prop is an object that looks like:

{
is_loading: bool,
component_name: string,
prop_name: string
}

So that component authors can determine when to show a loading spinner, and know which component in the chain caused the loading to happen (it will return the id of the component), and which prop is loading (for example children).

Community post: https://community.plot.ly/t/loading-states-api-and-a-loading-component-prerelease

Comment threadsrc/APIController.react.js Outdated
@valentijnniemanvalentijnnieman changed the title [WIP] Loading states apiLoading states apiNov 6, 2018
@chriddyp

Copy link
Copy Markdown
Member

For those following along in the community, here's a demo:
image
loading states

if (r.status === 'loading' && contains(id, r.controllerId)) {
isLoading = true;
loadingComponent = r.controllerId.split('.')[0];
loadingProp = r.controllerId.split('.')[1];

@T4rk1nT4rk1nNov 26, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can split only one time: [loadingProp, loadingComponent] = r.controllerId.split('.').

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Had a hard time figuring this out, that is not a proper map. It should be a for loop or a filter or a find.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the most appropriate would be a .forEach()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We have been using rambda elsewhere in dash-renderer even when a core javascript function exists for a task, so https://ramdajs.com/docs/#forEach would fit better

}
});

const thisRequest = requestQueue.filter(r => contains(id, r.controllerId));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The filter is here.

Comment threadpackage.json Outdated
@Marc-Andre-Rivet

Marc-Andre-Rivet commented Jan 8, 2019

Copy link
Copy Markdown
Contributor

Would like to see the following issues opened as follow ups:

  • enabling direct support of loading state in DCC components (loading_state + animation type)
  • integration testing support for dash-renderer (our tests are all at the behavioural / dash server level atm) -- test loading states, test loading states transition, test more complex graph shapes (Loading component with multiple children, Loading comp within a parent loading comp)
  • removing the 'Loading...' from dash.py

@Marc-Andre-Rivet

Marc-Andre-Rivet commented Jan 24, 2019

Copy link
Copy Markdown
Contributor

As discussed with @valentijnnieman, we are currently going for a mixed approach, supporting both

  • loading state as a data-attribute on the components + styleguide / css styling of loading comps
  • Loading component with 1 level deep loading_state support -- the user becomes responsible for putting items that are actually slow to load (e.g. no listening to children prop in Dash)

While the updated implementation looks promising, my main concern right now has to do with the mechanics of getting this to work -- the TreeContainer or the NotifyObserver needs to be hooked up to the store's requestQueue through a prop in order to determine the component's state -- the queue is impacted by user actions and changes often during back-and-forth with Dash. Since not all components are pure in html and dcc, or protected by a componentShouldUpdate method, this may cause a significant performance impact. This should be studied and minimized before the feature can be released / sent back for community testing. It's possible that the impact will be negligible but if not, we may have to expose the queue data differently so as to not trigger useless re-render.

@chriddyp

Copy link
Copy Markdown
Member

the TreeContainer or the NotifyObserver needs to be hooked up to the store's requestQueue through a prop in order to determine the component's state

I'm a little rusty on the redux details here, but I thought that any dispatch that happens will trigger an update. I don't believe that there isn't anything in NotifyObservers that says "If store.x changes, then update. But if store.y changes, don't update.". Does this match your understanding too or am I forgetting about something? If so, can you point me to the code where this happens?

So, if my understanding is right, the existing code is dispatching to update the request queue which is already cause re-rendering. Passing new props through in NotifyObserversComponent shouldn't change this.

Code reference: dispatch(setRequestQueue(

dispatch(setRequestQueue(concat(requestQueue,newRequestQueue)));

Connected NotifyObservers

/*
* NotifyObservers passes a connected `setProps` handler down to
* its child as a prop
*/
functionmapStateToProps(state){
return{
dependencies: state.dependenciesRequest.content,
paths: state.paths,
};
}
functionmapDispatchToProps(dispatch){
return{dispatch};
}
functionmergeProps(stateProps,dispatchProps,ownProps){
const{dispatch}=dispatchProps;
return{
id: ownProps.id,
children: ownProps.children,
dependencies: stateProps.dependencies,
paths: stateProps.paths,
setProps: functionsetProps(newProps){
constpayload={
props: newProps,
id: ownProps.id,
itempath: stateProps.paths[ownProps.id],
};
// Update this component's props
dispatch(updateProps(payload));
// Update output components that depend on this input
dispatch(notifyObservers({id: ownProps.id,props: newProps}));
},
};
}
functionNotifyObserversComponent({
children,
id,
paths,
dependencies,
setProps,
}){
constthisComponentSharesState=
dependencies&&
dependencies.find(
dependency=>
dependency.inputs.find(input=>input.id===id)||
dependency.state.find(state=>state.id===id)
);
/*
* Only pass in `setProps` if necessary.
* This allows component authors to skip computing unneeded data
* for `setProps`, which can be expensive.
* For example, consider `hoverData` for graphs. If it isn't
* actually used, then the component author can skip binding
* the events for the component.
*
* TODO - A nice enhancement would be to pass in the actual
* properties that are used into the component so that the
* component author can check for something like
* `subscribed_properties` instead of just `setProps`.
*/
constextraProps={};
if(
thisComponentSharesState&&
// there is a bug with graphs right now where
// the restyle listener gets assigned with a
// setProps function that was created before
// the item was added. only pass in setProps
// if the item's path exists for now.
paths[id]
){
extraProps.setProps=setProps;
}
if(!isEmpty(extraProps)){
returnReact.cloneElement(children,extraProps);
}
returnchildren;
}
NotifyObserversComponent.propTypes={
id: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
path: PropTypes.array.isRequired,
};
exportdefaultconnect(
mapStateToProps,
mapDispatchToProps,
mergeProps
)(NotifyObserversComponent);

@Marc-Andre-Rivet

Copy link
Copy Markdown
Contributor

@chriddyp No, your understanding seems to be correct. Adding the prop from the store does not have the impact I thought it would have. My thinking was that since N.Obs. had a new prop from the store, that would trigger extra renders but that assumption was incorrect. There are a few extra renders in the observer when first starting the app but after that it's a one to one match with previous behavior.

Thanks for the input.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

Released as prerelease version 0.18.0rc1.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

There seems to be an issue here that's causing the test_confirm_as_children in dash-core-components to fail. Haven't been able to figure this out yet.

@valentijnnieman

valentijnnieman commented Jan 31, 2019

Copy link
Copy Markdown
ContributorAuthor

Published 0.18.0rc3 which should fix some of the tests in DCC failing.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

Released as prerelease version 0.18.0rc4

Comment threadsimple.py Outdated
Comment threadCHANGELOG.md

## [0.18.0] - 2019-01-30
### Added
- Loading states API [#267](https://github.com/plotly/dash/issues/267)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For final merge, needs to be unreleased and version bump reverted in package.json

Comment threadsrc/TreeContainer.js Outdated
Comment threadsrc/TreeContainer.js Outdated
return nextProps.layout !== this.props.layout;
return (
nextProps.layout !== this.props.layout
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is checking only for the layout enough to update correctly in all cases? How does this cover the requestQueue updates? Or how is the requestQueue not necessary?

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.

I'm not sure about this. The main reason that I haven't added any nextProps.requestQueue checks is that it causes tests to fail. I'm guessing that re-rendering upon differences in the requestQueue prop causes some behaviour to be different - I'm seeing test_radio_buttons_callbacks_generating_children and test_hot_reload tests failing.

Comment threadsrc/TreeContainer.js
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.

6 participants

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

Loading states api - #93

Merged
valentijnnieman merged 48 commits into
masterfrom
loading_states_api
Feb 28, 2019
Merged

Loading states api#93
valentijnnieman merged 48 commits into
masterfrom
loading_states_api

Conversation

@valentijnnieman

@valentijnniemanvalentijnnieman commented Oct 30, 2018

Copy link
Copy Markdown
Contributor

Here's the first pass at a loading states api, where dash-renderer now provides a loading_state prop to components that are taking some time to load. It is used with a dist of this PR that provides a Loading component, that can wrap any other Dash component, making it display a loading spinner if it's not fully rendered. An example is included in simple.py, make sure to install the dev-requirements.txt before running it!

TODO:

  • Include which property is loading. Currently we strip the component's name, but it also includes the property that's loading, so we could pass that down as well.
  • Clean-up!

The loading_state prop is an object that looks like:

{
is_loading: bool,
component_name: string,
prop_name: string
}

So that component authors can determine when to show a loading spinner, and know which component in the chain caused the loading to happen (it will return the id of the component), and which prop is loading (for example children).

Community post: https://community.plot.ly/t/loading-states-api-and-a-loading-component-prerelease

Comment threadsrc/APIController.react.js Outdated
@valentijnniemanvalentijnnieman changed the title [WIP] Loading states apiLoading states apiNov 6, 2018
@chriddyp

Copy link
Copy Markdown
Member

For those following along in the community, here's a demo:
image
loading states

if (r.status === 'loading' && contains(id, r.controllerId)) {
isLoading = true;
loadingComponent = r.controllerId.split('.')[0];
loadingProp = r.controllerId.split('.')[1];

@T4rk1nT4rk1nNov 26, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can split only one time: [loadingProp, loadingComponent] = r.controllerId.split('.').

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Had a hard time figuring this out, that is not a proper map. It should be a for loop or a filter or a find.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the most appropriate would be a .forEach()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We have been using rambda elsewhere in dash-renderer even when a core javascript function exists for a task, so https://ramdajs.com/docs/#forEach would fit better

}
});

const thisRequest = requestQueue.filter(r => contains(id, r.controllerId));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The filter is here.

Comment threadpackage.json Outdated
@Marc-Andre-Rivet

Marc-Andre-Rivet commented Jan 8, 2019

Copy link
Copy Markdown
Contributor

Would like to see the following issues opened as follow ups:

  • enabling direct support of loading state in DCC components (loading_state + animation type)
  • integration testing support for dash-renderer (our tests are all at the behavioural / dash server level atm) -- test loading states, test loading states transition, test more complex graph shapes (Loading component with multiple children, Loading comp within a parent loading comp)
  • removing the 'Loading...' from dash.py

@Marc-Andre-Rivet

Marc-Andre-Rivet commented Jan 24, 2019

Copy link
Copy Markdown
Contributor

As discussed with @valentijnnieman, we are currently going for a mixed approach, supporting both

  • loading state as a data-attribute on the components + styleguide / css styling of loading comps
  • Loading component with 1 level deep loading_state support -- the user becomes responsible for putting items that are actually slow to load (e.g. no listening to children prop in Dash)

While the updated implementation looks promising, my main concern right now has to do with the mechanics of getting this to work -- the TreeContainer or the NotifyObserver needs to be hooked up to the store's requestQueue through a prop in order to determine the component's state -- the queue is impacted by user actions and changes often during back-and-forth with Dash. Since not all components are pure in html and dcc, or protected by a componentShouldUpdate method, this may cause a significant performance impact. This should be studied and minimized before the feature can be released / sent back for community testing. It's possible that the impact will be negligible but if not, we may have to expose the queue data differently so as to not trigger useless re-render.

@chriddyp

Copy link
Copy Markdown
Member

the TreeContainer or the NotifyObserver needs to be hooked up to the store's requestQueue through a prop in order to determine the component's state

I'm a little rusty on the redux details here, but I thought that any dispatch that happens will trigger an update. I don't believe that there isn't anything in NotifyObservers that says "If store.x changes, then update. But if store.y changes, don't update.". Does this match your understanding too or am I forgetting about something? If so, can you point me to the code where this happens?

So, if my understanding is right, the existing code is dispatching to update the request queue which is already cause re-rendering. Passing new props through in NotifyObserversComponent shouldn't change this.

Code reference: dispatch(setRequestQueue(

dispatch(setRequestQueue(concat(requestQueue,newRequestQueue)));

Connected NotifyObservers

/*
* NotifyObservers passes a connected `setProps` handler down to
* its child as a prop
*/
functionmapStateToProps(state){
return{
dependencies: state.dependenciesRequest.content,
paths: state.paths,
};
}
functionmapDispatchToProps(dispatch){
return{dispatch};
}
functionmergeProps(stateProps,dispatchProps,ownProps){
const{dispatch}=dispatchProps;
return{
id: ownProps.id,
children: ownProps.children,
dependencies: stateProps.dependencies,
paths: stateProps.paths,
setProps: functionsetProps(newProps){
constpayload={
props: newProps,
id: ownProps.id,
itempath: stateProps.paths[ownProps.id],
};
// Update this component's props
dispatch(updateProps(payload));
// Update output components that depend on this input
dispatch(notifyObservers({id: ownProps.id,props: newProps}));
},
};
}
functionNotifyObserversComponent({
children,
id,
paths,
dependencies,
setProps,
}){
constthisComponentSharesState=
dependencies&&
dependencies.find(
dependency=>
dependency.inputs.find(input=>input.id===id)||
dependency.state.find(state=>state.id===id)
);
/*
* Only pass in `setProps` if necessary.
* This allows component authors to skip computing unneeded data
* for `setProps`, which can be expensive.
* For example, consider `hoverData` for graphs. If it isn't
* actually used, then the component author can skip binding
* the events for the component.
*
* TODO - A nice enhancement would be to pass in the actual
* properties that are used into the component so that the
* component author can check for something like
* `subscribed_properties` instead of just `setProps`.
*/
constextraProps={};
if(
thisComponentSharesState&&
// there is a bug with graphs right now where
// the restyle listener gets assigned with a
// setProps function that was created before
// the item was added. only pass in setProps
// if the item's path exists for now.
paths[id]
){
extraProps.setProps=setProps;
}
if(!isEmpty(extraProps)){
returnReact.cloneElement(children,extraProps);
}
returnchildren;
}
NotifyObserversComponent.propTypes={
id: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
path: PropTypes.array.isRequired,
};
exportdefaultconnect(
mapStateToProps,
mapDispatchToProps,
mergeProps
)(NotifyObserversComponent);

@Marc-Andre-Rivet

Copy link
Copy Markdown
Contributor

@chriddyp No, your understanding seems to be correct. Adding the prop from the store does not have the impact I thought it would have. My thinking was that since N.Obs. had a new prop from the store, that would trigger extra renders but that assumption was incorrect. There are a few extra renders in the observer when first starting the app but after that it's a one to one match with previous behavior.

Thanks for the input.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

Released as prerelease version 0.18.0rc1.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

There seems to be an issue here that's causing the test_confirm_as_children in dash-core-components to fail. Haven't been able to figure this out yet.

@valentijnnieman

valentijnnieman commented Jan 31, 2019

Copy link
Copy Markdown
ContributorAuthor

Published 0.18.0rc3 which should fix some of the tests in DCC failing.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

Released as prerelease version 0.18.0rc4

Comment threadsimple.py Outdated
Comment threadCHANGELOG.md

## [0.18.0] - 2019-01-30
### Added
- Loading states API [#267](https://github.com/plotly/dash/issues/267)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For final merge, needs to be unreleased and version bump reverted in package.json

Comment threadsrc/TreeContainer.js Outdated
Comment threadsrc/TreeContainer.js Outdated
return nextProps.layout !== this.props.layout;
return (
nextProps.layout !== this.props.layout
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is checking only for the layout enough to update correctly in all cases? How does this cover the requestQueue updates? Or how is the requestQueue not necessary?

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.

I'm not sure about this. The main reason that I haven't added any nextProps.requestQueue checks is that it causes tests to fail. I'm guessing that re-rendering upon differences in the requestQueue prop causes some behaviour to be different - I'm seeing test_radio_buttons_callbacks_generating_children and test_hot_reload tests failing.

Comment threadsrc/TreeContainer.js
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.

6 participants

@valentijnnieman@chriddyp@rmarren1@T4rk1n@Marc-Andre-Rivet@nicolaskruchten