From bab54da11e5dbef6f3b19739e0eb9e1d1034936a Mon Sep 17 00:00:00 2001 From: sergesemashko Date: Sun, 3 Apr 2016 19:55:23 -0400 Subject: [PATCH 1/3] [issue #45] - Performance optimizations, reduced wasted renders - Decomposed LogMonitor, created LogMonitorEntryList, so log array is recalculated only when props are updated - added debounced scroll listener to container, so scrollTop is updated only when scroll ends --- src/LogMonitor.js | 76 ++++++++++++++++++++------------------ src/LogMonitorEntryList.js | 67 +++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 36 deletions(-) create mode 100644 src/LogMonitorEntryList.js diff --git a/src/LogMonitor.js b/src/LogMonitor.js index c760b92..66e4e38 100644 --- a/src/LogMonitor.js +++ b/src/LogMonitor.js @@ -1,11 +1,11 @@ import React, { PropTypes, Component } from 'react'; -import LogMonitorEntry from './LogMonitorEntry'; import LogMonitorButton from './LogMonitorButton'; import shouldPureComponentUpdate from 'react-pure-render/function'; import * as themes from 'redux-devtools-themes'; import { ActionCreators } from 'redux-devtools'; import { updateScrollTop } from './actions'; import reducer from './reducers'; +import LogMonitorEntryList from './LogMonitorEntryList'; const { reset, rollback, commit, sweep, toggleAction } = ActionCreators; @@ -39,6 +39,14 @@ const styles = { } }; +const debounced = (func, wait) => { + let timeout; + return () => { + clearTimeout(timeout); + timeout = setTimeout(func, wait); + }; +}; + export default class LogMonitor extends Component { static update = reducer; @@ -72,6 +80,11 @@ export default class LogMonitor extends Component { shouldComponentUpdate = shouldPureComponentUpdate; + updateScrollTop = debounced(() => { + const node = this.refs.container; + this.props.dispatch(updateScrollTop(node ? node.scrollTop : 0)); + }, 500); + constructor(props) { super(props); this.handleToggleAction = this.handleToggleAction.bind(this); @@ -101,7 +114,7 @@ export default class LogMonitor extends Component { if (this.props.preserveScrollTop) { node.scrollTop = this.props.monitorState.initialScrollTop; - this.interval = setInterval(::this.updateScrollTop, 1000); + node.addEventListener('scroll', this.updateScrollTop); } else { this.scrollDown = true; this.scroll(); @@ -109,14 +122,10 @@ export default class LogMonitor extends Component { } componentWillUnmount() { - if (this.interval) { - clearInterval(this.interval); - } - } - - updateScrollTop() { const node = this.refs.container; - this.props.dispatch(updateScrollTop(node ? node.scrollTop : 0)); + if (node && this.props.preserveScrollTop) { + node.addEventListener('scroll', this.updateScrollTop); + } } componentWillReceiveProps(nextProps) { @@ -176,33 +185,28 @@ export default class LogMonitor extends Component { } render() { - const elements = []; const theme = this.getTheme(); - const { actionsById, skippedActionIds, stagedActionIds, computedStates, select } = this.props; - - for (let i = 0; i < stagedActionIds.length; i++) { - const actionId = stagedActionIds[i]; - const action = actionsById[actionId].action; - const { state, error } = computedStates[i]; - let previousState; - if (i > 0) { - previousState = computedStates[i - 1].state; - } - elements.push( - -1} - error={error} - expandActionRoot={this.props.expandActionRoot} - expandStateRoot={this.props.expandStateRoot} - onActionClick={this.handleToggleAction} /> - ); - } + const { + actionsById, + skippedActionIds, + stagedActionIds, + computedStates, + select, + expandActionRoot, + expandStateRoot + } = this.props; + + const entryListProps = { + theme, + actionsById, + skippedActionIds, + stagedActionIds, + computedStates, + select, + expandActionRoot, + expandStateRoot, + onActionClick: this.handleToggleAction + }; return (
@@ -233,7 +237,7 @@ export default class LogMonitor extends Component {
- {elements} +
); diff --git a/src/LogMonitorEntryList.js b/src/LogMonitorEntryList.js new file mode 100644 index 0000000..5b73ba2 --- /dev/null +++ b/src/LogMonitorEntryList.js @@ -0,0 +1,67 @@ +import React, { PropTypes, Component } from 'react'; +import LogMonitorEntry from './LogMonitorEntry'; +import shouldPureComponentUpdate from 'react-pure-render/function'; + +export default class LogMonitorEntryList extends Component { + + static propTypes = { + actionsById: PropTypes.object, + computedStates: PropTypes.array, + stagedActionIds: PropTypes.array, + skippedActionIds: PropTypes.array, + + select: PropTypes.func.isRequired, + onActionClick: PropTypes.func.isRequired, + theme: PropTypes.oneOfType([ + PropTypes.object, + PropTypes.string + ]), + expandActionRoot: PropTypes.bool, + expandStateRoot: PropTypes.bool + }; + + shouldComponentUpdate = shouldPureComponentUpdate; + + render() { + const elements = []; + const { + theme, + actionsById, + computedStates, + select, + skippedActionIds, + stagedActionIds, + expandActionRoot, + expandStateRoot, + onActionClick + } = this.props; + + for (let i = 0; i < stagedActionIds.length; i++) { + const actionId = stagedActionIds[i]; + const action = actionsById[actionId].action; + const { state, error } = computedStates[i]; + let previousState; + if (i > 0) { + previousState = computedStates[i - 1].state; + } + elements.push( + -1} + error={error} + expandActionRoot={expandActionRoot} + expandStateRoot={expandStateRoot} + onActionClick={onActionClick} /> + ); + } + + return (
+ {elements} +
); + } +} From 6d02f62d25ecd1b5def427bb53d1f3324a3a215c Mon Sep 17 00:00:00 2001 From: sergesemashko Date: Sun, 3 Apr 2016 21:43:23 -0400 Subject: [PATCH 2/3] - placed removeEventListener to be called on componentWillUnmount() --- src/LogMonitor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LogMonitor.js b/src/LogMonitor.js index 66e4e38..8700961 100644 --- a/src/LogMonitor.js +++ b/src/LogMonitor.js @@ -124,7 +124,7 @@ export default class LogMonitor extends Component { componentWillUnmount() { const node = this.refs.container; if (node && this.props.preserveScrollTop) { - node.addEventListener('scroll', this.updateScrollTop); + node.removeEventListener('scroll', this.updateScrollTop); } } From 82c72ba3b4163767b90c86065d740ae07858458e Mon Sep 17 00:00:00 2001 From: sergesemashko Date: Sun, 10 Apr 2016 12:23:02 -0400 Subject: [PATCH 3/3] - import debounce from lodash --- package.json | 1 + src/LogMonitor.js | 11 ++--------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 622f630..e71fcd7 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "redux-devtools": "^3.0.0" }, "dependencies": { + "lodash.debounce": "^4.0.4", "react-json-tree": "^0.6.5", "react-pure-render": "^1.0.2", "redux-devtools-themes": "^1.0.0" diff --git a/src/LogMonitor.js b/src/LogMonitor.js index 8700961..0049315 100644 --- a/src/LogMonitor.js +++ b/src/LogMonitor.js @@ -6,6 +6,7 @@ import { ActionCreators } from 'redux-devtools'; import { updateScrollTop } from './actions'; import reducer from './reducers'; import LogMonitorEntryList from './LogMonitorEntryList'; +import debounce from 'lodash.debounce'; const { reset, rollback, commit, sweep, toggleAction } = ActionCreators; @@ -39,14 +40,6 @@ const styles = { } }; -const debounced = (func, wait) => { - let timeout; - return () => { - clearTimeout(timeout); - timeout = setTimeout(func, wait); - }; -}; - export default class LogMonitor extends Component { static update = reducer; @@ -80,7 +73,7 @@ export default class LogMonitor extends Component { shouldComponentUpdate = shouldPureComponentUpdate; - updateScrollTop = debounced(() => { + updateScrollTop = debounce(() => { const node = this.refs.container; this.props.dispatch(updateScrollTop(node ? node.scrollTop : 0)); }, 500);