Skip to content

Commit b48a712

Browse files
committed
Dramatically increase performance when rendering a large number of nodes
Resolves #14.
1 parent 4df3b60 commit b48a712

File tree

4 files changed

+74
-22
lines changed

4 files changed

+74
-22
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
* [#32]: Allow customization of `className` at the node level
88
* [#30]: Add `showNodeIcon` property to optionally remove node icons
99

10+
### Other
11+
12+
* [#14]: Component performance when rendering and updating a large number of nodes has been significantly increased
13+
1014
## [v0.5.2](https://github.com/jakezatecky/react-checkbox-tree/compare/v0.5.1...v0.5.2) (2017-05-03)
1115

1216
### Bug Fixes

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ class Widget extends React.Component {
8585
}
8686
```
8787

88+
All node objects **must** have a unique `value`. This value is serialized into the `checked` and `expanded` arrays and is also used for performance optimizations.
89+
8890
### Properties
8991

9092
| Property | Type | Description | Default |

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
},
6060
"dependencies": {
6161
"classnames": "^2.2.5",
62+
"lodash": "^4.17.4",
6263
"prop-types": "^15.5.8",
6364
"shortid": "^2.2.6"
6465
}

src/js/CheckboxTree.js

Lines changed: 67 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { isEqual } from 'lodash';
12
import PropTypes from 'prop-types';
23
import React from 'react';
34
import shortid from 'shortid';
@@ -34,32 +35,47 @@ class CheckboxTree extends React.Component {
3435
constructor(props) {
3536
super(props);
3637

38+
this.id = `rct-${shortid.generate()}`;
39+
this.nodes = {};
40+
41+
this.flattenNodes(props.nodes);
42+
this.unserializeLists({
43+
checked: props.checked,
44+
expanded: props.expanded,
45+
});
46+
3747
this.onCheck = this.onCheck.bind(this);
3848
this.onExpand = this.onExpand.bind(this);
49+
}
3950

40-
this.id = `rct-${shortid.generate()}`;
51+
componentWillReceiveProps({ nodes, checked, expanded }) {
52+
if (!isEqual(this.props.nodes, nodes)) {
53+
this.flattenNodes(nodes);
54+
}
55+
56+
this.unserializeLists({ checked, expanded });
4157
}
4258

4359
onCheck(node) {
44-
const { checked, onCheck } = this.props;
60+
const { onCheck } = this.props;
4561

46-
onCheck(this.toggleChecked([...checked], node, node.checked));
62+
this.toggleChecked(node, node.checked);
63+
onCheck(this.serializeList('checked'));
4764
}
4865

4966
onExpand(node) {
50-
const { expanded, onExpand } = this.props;
67+
const { onExpand } = this.props;
5168

52-
onExpand(this.toggleNode([...expanded], node, node.expanded));
69+
this.toggleNode('expanded', node, node.expanded);
70+
onExpand(this.serializeList('expanded'));
5371
}
5472

5573
getFormattedNodes(nodes) {
56-
const { checked, expanded } = this.props;
57-
5874
return nodes.map((node) => {
5975
const formatted = { ...node };
6076

61-
formatted.checked = checked.indexOf(node.value) > -1;
62-
formatted.expanded = expanded.indexOf(node.value) > -1;
77+
formatted.checked = this.nodes[node.value].checked;
78+
formatted.expanded = this.nodes[node.value].expanded;
6379

6480
if (Array.isArray(node.children) && node.children.length > 0) {
6581
formatted.children = this.getFormattedNodes(formatted.children);
@@ -87,29 +103,58 @@ class CheckboxTree extends React.Component {
87103
return 0;
88104
}
89105

90-
toggleChecked(checked, node, isChecked) {
106+
toggleChecked(node, isChecked) {
91107
if (node.children !== null) {
92108
// Percolate check status down to all children
93109
node.children.forEach((child) => {
94-
this.toggleChecked(checked, child, isChecked);
110+
this.toggleChecked(child, isChecked);
95111
});
96112
} else {
97113
// Set leaf to check/unchecked state
98-
this.toggleNode(checked, node, isChecked);
114+
this.toggleNode('checked', node, isChecked);
99115
}
100-
101-
return checked;
102116
}
103117

104-
toggleNode(list, node, toggleValue) {
105-
const index = list.indexOf(node.value);
118+
toggleNode(key, node, toggleValue) {
119+
this.nodes[node.value][key] = toggleValue;
120+
}
106121

107-
if (index > -1 && !toggleValue) {
108-
list.splice(index, 1);
109-
} else if (index === -1 && toggleValue) {
110-
list.push(node.value);
122+
flattenNodes(nodes) {
123+
if (!Array.isArray(nodes) || nodes.length === 0) {
124+
return;
111125
}
112126

127+
nodes.forEach((node) => {
128+
this.nodes[node.value] = {};
129+
this.flattenNodes(node.children);
130+
});
131+
}
132+
133+
unserializeLists(lists) {
134+
// Reset values to false
135+
Object.keys(this.nodes).forEach((value) => {
136+
Object.keys(lists).forEach((listKey) => {
137+
this.nodes[value][listKey] = false;
138+
});
139+
});
140+
141+
// Unserialize values and set their nodes to true
142+
Object.keys(lists).forEach((listKey) => {
143+
lists[listKey].forEach((value) => {
144+
this.nodes[value][listKey] = true;
145+
});
146+
});
147+
}
148+
149+
serializeList(key) {
150+
const list = [];
151+
152+
Object.keys(this.nodes).forEach((value) => {
153+
if (this.nodes[value][key]) {
154+
list.push(value);
155+
}
156+
});
157+
113158
return list;
114159
}
115160

@@ -134,8 +179,8 @@ class CheckboxTree extends React.Component {
134179
}
135180

136181
renderTreeNodes(nodes) {
137-
const treeNodes = nodes.map((node, index) => {
138-
const key = `${index}-${node.value}`;
182+
const treeNodes = nodes.map((node) => {
183+
const key = `${node.value}`;
139184
const checked = this.getCheckState(node);
140185
const children = this.renderChildNodes(node);
141186

0 commit comments

Comments
 (0)