Skip to content
This repository was archived by the owner on Jun 4, 2024. It is now read-only.

Change renderer setProps assignation logic #126

Merged
merged 28 commits into from
Mar 11, 2019
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ jobs:
name: Install dependencies (dash)
command: |
git clone [email protected]:plotly/dash.git
git clone [email protected]:plotly/dash-core-components.git
git clone [email protected]:plotly/dash-html-components.git
git clone -b renderer-setprops-20rc1 [email protected]:plotly/dash-core-components.git
git clone -b renderer-setprops-20rc1 [email protected]:plotly/dash-html-components.git
git clone [email protected]:plotly/dash-table.git
. venv/bin/activate
pip install -e ./dash --quiet
Expand Down
Binary file added dash-0.39.0rc1.tar.gz
Binary file not shown.
Binary file added dash_core_components-0.44.0rc1.tar.gz
Binary file not shown.
Binary file added dash_html_components-0.14.0rc1.tar.gz
Binary file not shown.
8 changes: 4 additions & 4 deletions dash_renderer/dash_renderer.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dash_renderer/dash_renderer.min.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/APIController.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ class UnconnectedContainer extends Component {
} else if (appLifecycle === getAppState('HYDRATED')) {
return (
<div id="_dash-app-content">
<TreeContainer layout={layout} />
<TreeContainer _dashprivate_layout={layout} />
</div>
);
}
Expand Down
293 changes: 182 additions & 111 deletions src/TreeContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,144 +3,215 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import Registry from './registry';
import NotifyObservers from './components/core/NotifyObservers.react';
import {connect} from 'react-redux';
import {
isNil,
omit,
contains,
isEmpty,
filter,
forEach,
isEmpty,
isNil,
keysIn,
mergeAll,
omit,
pick,
propOr,
type,
has,
type
} from 'ramda';
import {STATUS} from './constants/constants';
import { notifyObservers, updateProps } from './actions';

const SIMPLE_COMPONENT_TYPES = ['String', 'Number', 'Null', 'Boolean'];

class TreeContainer extends Component {
shouldComponentUpdate(nextProps) {
return nextProps.layout !== this.props.layout;
}
getChildren(components) {
if (!components) {
return null;
}

render() {
return recursivelyRender(this.props.layout, this.props.requestQueue);
if (!Array.isArray(components)) {
return contains(type(components), SIMPLE_COMPONENT_TYPES) ?
components :
(<AugmentedTreeContainer
key={components && components.props && components.props.id}
_dashprivate_layout={components}
/>);
}

return components.map(child => contains(type(child), SIMPLE_COMPONENT_TYPES) ?
child :
(<AugmentedTreeContainer
key={child && child.props && child.props.id}
_dashprivate_layout={child}
/>));
}
}

TreeContainer.propTypes = {
layout: PropTypes.object,
requestQueue: PropTypes.object,
};
getComponent(_dashprivate_layout, children, loading_state, setProps) {
if (isEmpty(_dashprivate_layout)) {
return null;
}

function recursivelyRender(component, requestQueue) {
if (contains(type(component), ['String', 'Number', 'Null', 'Boolean'])) {
return component;
if (contains(type(_dashprivate_layout), SIMPLE_COMPONENT_TYPES)) {
return _dashprivate_layout;
}

if (!_dashprivate_layout.type) {
/* eslint-disable no-console */
console.error(type(_dashprivate_layout), _dashprivate_layout);
/* eslint-enable no-console */
throw new Error('component.type is undefined');
}
if (!_dashprivate_layout.namespace) {
/* eslint-disable no-console */
console.error(type(_dashprivate_layout), _dashprivate_layout);
/* eslint-enable no-console */
throw new Error('component.namespace is undefined');
}
const element = Registry.resolve(_dashprivate_layout.type, _dashprivate_layout.namespace);

return Array.isArray(children) ?
React.createElement(
element,
mergeAll([
omit(['children'], _dashprivate_layout.props),
{ loading_state, setProps }
]),
...children
) :
React.createElement(
element,
mergeAll([
omit(['children'], _dashprivate_layout.props),
{ loading_state, setProps }
]),
...[children]
);
}

if (isEmpty(component)) {
return null;
getLoadingState(id, requestQueue) {
// loading prop coming from TreeContainer
let isLoading = false;
let loadingProp;
let loadingComponent;

if (requestQueue && requestQueue.filter) {
forEach(r => {
const controllerId = isNil(r.controllerId) ? '' : r.controllerId;
if (r.status === 'loading' && contains(id, controllerId)) {
isLoading = true;
[loadingComponent, loadingProp] = r.controllerId.split('.');
}
}, requestQueue);

const thisRequest = requestQueue.filter(r => {
const controllerId = isNil(r.controllerId) ? '' : r.controllerId;
return contains(id, controllerId);
});
if (thisRequest.status === STATUS.OK) {
isLoading = false;
}
}

// Set loading state
return {
is_loading: isLoading,
prop_name: loadingProp,
component_name: loadingComponent,
};
}

// Create list of child elements
let children;

const componentProps = propOr({}, 'props', component);

if (
!has('props', component) ||
!has('children', component.props) ||
typeof component.props.children === 'undefined'
) {
// No children
children = [];
} else if (
contains(type(component.props.children), [
'String',
'Number',
'Null',
'Boolean',
])
) {
children = [component.props.children];
} else {
// One or multiple objects
// Recursively render the tree
// TODO - I think we should pass in `key` here.
children = (Array.isArray(componentProps.children)
? componentProps.children
: [componentProps.children]
).map(child => recursivelyRender(child, requestQueue));
getSetProps() {
return newProps => {
const {
_dashprivate_dependencies,
_dashprivate_dispatch,
_dashprivate_paths
} = this.props;

const id = this.getLayoutProps().id;

// Identify the modified props that are required for callbacks
const watchedKeys = filter(key =>
_dashprivate_dependencies &&
_dashprivate_dependencies.find(dependency =>
dependency.inputs.find(input => input.id === id && input.property === key) ||
dependency.state.find(state => state.id === id && state.property === key)
)
)(keysIn(newProps));

// Always update this component's props
_dashprivate_dispatch(updateProps({
props: newProps,
id: id,
itempath: _dashprivate_paths[id]
}));

// Only dispatch changes to Dash if a watched prop changed
if (watchedKeys.length) {
_dashprivate_dispatch(notifyObservers({
id: id,
props: pick(watchedKeys)(newProps)
}));
}

};
}

if (!component.type) {
/* eslint-disable no-console */
console.error(type(component), component);
/* eslint-enable no-console */
throw new Error('component.type is undefined');
shouldComponentUpdate(nextProps) {
return nextProps._dashprivate_layout !== this.props._dashprivate_layout;
}
if (!component.namespace) {
/* eslint-disable no-console */
console.error(type(component), component);
/* eslint-enable no-console */
throw new Error('component.namespace is undefined');

getLayoutProps() {
return propOr({}, 'props', this.props._dashprivate_layout);
}
const element = Registry.resolve(component.type, component.namespace);

const parent = React.createElement(
element,
omit(['children'], component.props),
...children
);

// loading prop coming from TreeContainer
let isLoading = false;
let loadingProp;
let loadingComponent;

const id = componentProps.id;

if (requestQueue && requestQueue.filter) {
forEach(r => {
const controllerId = isNil(r.controllerId) ? '' : r.controllerId;
if (r.status === 'loading' && contains(id, controllerId)) {
isLoading = true;
[loadingComponent, loadingProp] = r.controllerId.split('.');
}
}, requestQueue);

const thisRequest = requestQueue.filter(r => {
const controllerId = isNil(r.controllerId) ? '' : r.controllerId;
return contains(id, controllerId);
});
if (thisRequest.status === STATUS.OK) {
isLoading = false;
}

render() {
const {
_dashprivate_dispatch,
_dashprivate_layout,
_dashprivate_requestQueue
} = this.props;

const layoutProps = this.getLayoutProps();

const children = this.getChildren(layoutProps.children);
const loadingState = this.getLoadingState(layoutProps.id, _dashprivate_requestQueue);
const setProps = this.getSetProps(_dashprivate_dispatch);

return this.getComponent(_dashprivate_layout, children, loadingState, setProps);
}
}

// Set loading state
const loading_state = {
is_loading: isLoading,
prop_name: loadingProp,
component_name: loadingComponent,
};
TreeContainer.propTypes = {
_dashprivate_dependencies: PropTypes.any,
_dashprivate_dispatch: PropTypes.func,
_dashprivate_layout: PropTypes.object,
_dashprivate_paths: PropTypes.any,
_dashprivate_requestQueue: PropTypes.object,
};

return (
<NotifyObservers
key={componentProps.id}
id={componentProps.id}
loading_state={loading_state}
>
{parent}
</NotifyObservers>
);
function mapDispatchToProps(dispatch) {
return { dispatch };
}

function mapStateToProps(state, ownProps) {
function mapStateToProps(state) {
return {
layout: ownProps.layout,
loading: ownProps.loading,
requestQueue: state.requestQueue,
dependencies: state.dependenciesRequest.content,
paths: state.paths,
requestQueue: state.requestQueue
};
}

export default connect(mapStateToProps)(TreeContainer);
function mergeProps(stateProps, dispatchProps, ownProps) {
return {
_dashprivate_dependencies: stateProps.dependencies,
_dashprivate_dispatch: dispatchProps.dispatch,
_dashprivate_layout: ownProps._dashprivate_layout,
_dashprivate_loading: ownProps._dashprivate_loading,
_dashprivate_paths: stateProps.paths,
_dashprivate_requestQueue: stateProps.requestQueue,
};
}

export const AugmentedTreeContainer = connect(mapStateToProps, mapDispatchToProps, mergeProps)(TreeContainer);

export default AugmentedTreeContainer;
Loading