From 2655797a6b9c8845c55d51491db769d2d7e271a2 Mon Sep 17 00:00:00 2001 From: Parmesh Krishen Date: Sun, 31 Dec 2017 15:22:40 -0600 Subject: [PATCH] Adding support for maintaining an immutable state for the tree --- example/app.js | 31 +++++++++++++++---------------- package.json | 1 + src/components/node.js | 14 +++++++------- src/components/treebeard.js | 16 ++++++++++++++-- src/index.js | 5 +++-- 5 files changed, 40 insertions(+), 27 deletions(-) diff --git a/example/app.js b/example/app.js index 9787955..45b82bb 100644 --- a/example/app.js +++ b/example/app.js @@ -4,7 +4,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import ReactDOM from 'react-dom'; import {StyleRoot} from 'radium'; -import {Treebeard, decorators} from '../src/index'; +import {Treebeard, decorators, makeNewData} from '../src/index'; import data from './data'; import styles from './styles'; @@ -22,7 +22,6 @@ decorators.Header = ({style, node}) => {
- {node.name}
@@ -48,32 +47,32 @@ NodeViewer.propTypes = { class DemoTree extends React.Component { constructor() { super(); - this.state = {data}; this.onToggle = this.onToggle.bind(this); } - onToggle(node, toggled) { - const {cursor} = this.state; - - if (cursor) { - cursor.active = false; - } - - node.active = true; + onToggle(node, toggled, nodePath) { + const {data: oldData, cursor, cursorPath} = this.state; + const nodeDiff = {active: true}; if (node.children) { - node.toggled = toggled; + nodeDiff.toggled = toggled; } - - this.setState({cursor: node}); + const newNode = Object.assign({}, node, nodeDiff); + let newData = oldData; + if (cursor && cursorPath) { + newData = makeNewData(newData, cursorPath, Object.assign({}, cursor, {active: false})); + } + newData = makeNewData(newData, nodePath, newNode); + this.setState({data: newData, cursor: newNode, cursorPath: nodePath}); } onFilterMouseUp(e) { + const {data: stateData} = this.state; const filter = e.target.value.trim(); if (!filter) { - return this.setState({data}); + return; } - var filtered = filters.filterTree(data, filter); + let filtered = filters.filterTree(stateData, filter); filtered = filters.expandFilteredNodes(filtered, filter); this.setState({data: filtered}); } diff --git a/package.json b/package.json index c7be081..153e3e2 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "dependencies": { "babel-runtime": "^6.23.0", "deep-equal": "^1.0.1", + "lodash": "^4.17.4", "prop-types": "^15.5.8", "radium": "^0.19.0", "shallowequal": "^0.2.2", diff --git a/src/components/node.js b/src/components/node.js index eb1c5ca..f1588fd 100644 --- a/src/components/node.js +++ b/src/components/node.js @@ -14,11 +14,11 @@ class TreeNode extends React.Component { } onClick() { - const {node, onToggle} = this.props; + const {node, nodePath, onToggle} = this.props; const {toggled} = node; if (onToggle) { - onToggle(node, !toggled); + onToggle(node, !toggled, nodePath); } } @@ -53,7 +53,6 @@ class TreeNode extends React.Component {
  • this.topLevelRef = ref} style={style.base}> {this.renderHeader(decorators, animations)} - {this.renderDrawer(decorators, animations)}
  • ); @@ -90,14 +89,13 @@ class TreeNode extends React.Component { } renderChildren(decorators) { - const {animations, decorators: propDecorators, node, style} = this.props; - + const {animations, decorators: propDecorators, node, nodePath, style} = this.props; if (node.loading) { return this.renderLoading(decorators); } - let children = node.children; - if (!Array.isArray(children)) { + const isChildrenArray = Array.isArray(node.children); + if (!isChildrenArray) { children = children ? [children] : []; } @@ -109,6 +107,7 @@ class TreeNode extends React.Component { decorators={propDecorators} key={child.id || index} node={child} + nodePath={isChildrenArray ? nodePath.concat('children', String(index)) : nodePath.concat('children')} style={style}/> )} @@ -139,6 +138,7 @@ class TreeNode extends React.Component { TreeNode.propTypes = { style: PropTypes.object.isRequired, node: PropTypes.object.isRequired, + nodePath: PropTypes.arrayOf(PropTypes.string).isRequired, decorators: PropTypes.object.isRequired, animations: PropTypes.oneOfType([ PropTypes.object, diff --git a/src/components/treebeard.js b/src/components/treebeard.js index 2e15254..5e55d2a 100644 --- a/src/components/treebeard.js +++ b/src/components/treebeard.js @@ -2,21 +2,31 @@ import React from 'react'; import PropTypes from 'prop-types'; +import {cloneDeep, set} from 'lodash'; import TreeNode from './node'; import defaultDecorators from './decorators'; import defaultTheme from '../themes/default'; import defaultAnimations from '../themes/animations'; +const makeNewData = (oldData, nodePath, newNode) => { + if (!(nodePath && nodePath.length)) { + return Object.assign({}, oldData, newNode); + } + return set(cloneDeep(oldData), nodePath, newNode); +}; + class TreeBeard extends React.Component { render() { const {animations, decorators, data: propsData, onToggle, style} = this.props; let data = propsData; - // Support Multiple Root Nodes. Its not formally a tree, but its a use-case. - if (!Array.isArray(data)) { + // Support Multiple Root Nodes. It's not formally a tree, but it's a use-case. + const isDataArray = Array.isArray(propsData); + if (!isDataArray) { data = [data]; } + return (