Skip to content

Commit 0f3d550

Browse files
author
Francisco Vergara
authored
Allow adding children to node without refreshing the whole tree (#417)
1 parent e9a3237 commit 0f3d550

File tree

3 files changed

+41
-3
lines changed

3 files changed

+41
-3
lines changed

src/Node/index.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import React, { SyntheticEvent } from 'react';
22
import { HierarchyPointNode } from 'd3-hierarchy';
33
import { select } from 'd3-selection';
4-
5-
import { Orientation, Point, TreeNodeDatum, RenderCustomNodeElementFn } from '../types/common.js';
4+
import { Orientation, Point, TreeNodeDatum, RawNodeDatum, RenderCustomNodeElementFn } from '../types/common.js';
65
import DefaultNodeElement from './DefaultNodeElement.js';
76

87
type NodeEventHandler = (
@@ -30,6 +29,7 @@ type NodeProps = {
3029
onNodeMouseOut: NodeEventHandler;
3130
subscriptions: object;
3231
centerNode: (hierarchyPointNode: HierarchyPointNode<TreeNodeDatum>) => void;
32+
handleAddChildrenToNode: (nodeId: string, children: RawNodeDatum[]) => void;
3333
};
3434

3535
type NodeState = {
@@ -141,6 +141,7 @@ export default class Node extends React.Component<NodeProps, NodeState> {
141141
onNodeClick: this.handleOnClick,
142142
onNodeMouseOver: this.handleOnMouseOver,
143143
onNodeMouseOut: this.handleOnMouseOut,
144+
addChildren: this.handleAddChildren,
144145
};
145146

146147
return renderNode(nodeProps);
@@ -164,6 +165,10 @@ export default class Node extends React.Component<NodeProps, NodeState> {
164165
this.props.onNodeMouseOut(this.props.hierarchyPointNode, evt);
165166
};
166167

168+
handleAddChildren = childrenData => {
169+
this.props.handleAddChildrenToNode(this.props.data.__rd3t.id, childrenData);
170+
};
171+
167172
componentWillLeave(done) {
168173
const { orientation, transitionDuration, position, parent } = this.props;
169174
const transform = this.setTransform(position, parent, orientation, true);

src/Tree/index.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type TreeState = {
1919
d3: { translate: Point; scale: number };
2020
isTransitioning: boolean;
2121
isInitialRenderForDataset: boolean;
22+
dataKey: string;
2223
};
2324

2425
class Tree extends React.Component<TreeProps, TreeState> {
@@ -54,6 +55,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
5455
hasInteractiveNodes: false,
5556
dimensions: undefined,
5657
centeringTransitionDuration: 800,
58+
dataKey: undefined,
5759
};
5860

5961
state: TreeState = {
@@ -62,6 +64,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
6264
d3: Tree.calculateD3Geometry(this.props),
6365
isTransitioning: false,
6466
isInitialRenderForDataset: true,
67+
dataKey: this.props.dataKey,
6568
};
6669

6770
private internalState = {
@@ -75,11 +78,14 @@ class Tree extends React.Component<TreeProps, TreeState> {
7578
static getDerivedStateFromProps(nextProps: TreeProps, prevState: TreeState) {
7679
let derivedState: Partial<TreeState> = null;
7780
// Clone new data & assign internal properties if `data` object reference changed.
78-
if (nextProps.data !== prevState.dataRef) {
81+
// If the dataKey was present but didn't change, then we don't need to re-render the tree
82+
const dataKeyChanged = !nextProps.dataKey || prevState.dataKey !== nextProps.dataKey;
83+
if (nextProps.data !== prevState.dataRef && dataKeyChanged) {
7984
derivedState = {
8085
dataRef: nextProps.data,
8186
data: Tree.assignInternalProperties(clone(nextProps.data)),
8287
isInitialRenderForDataset: true,
88+
dataKey: nextProps.dataKey,
8389
};
8490
}
8591
const d3 = Tree.calculateD3Geometry(nextProps);
@@ -322,6 +328,23 @@ class Tree extends React.Component<TreeProps, TreeState> {
322328
}
323329
};
324330

331+
handleAddChildrenToNode = (nodeId: string, childrenData: RawNodeDatum[]) => {
332+
const data = clone(this.state.data);
333+
const matches = this.findNodesById(nodeId, data, []);
334+
335+
if (matches.length > 0) {
336+
const targetNodeDatum = matches[0];
337+
338+
const depth = targetNodeDatum.__rd3t.depth;
339+
const formattedChildren = clone(childrenData).map((node: RawNodeDatum) =>
340+
Tree.assignInternalProperties([node], depth + 1)
341+
);
342+
targetNodeDatum.children.push(...formattedChildren.flat());
343+
344+
this.setState({ data });
345+
}
346+
};
347+
325348
/**
326349
* Handles the user-defined `onNodeClick` function.
327350
*/
@@ -576,6 +599,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
576599
onNodeClick={this.handleOnNodeClickCb}
577600
onNodeMouseOver={this.handleOnNodeMouseOverCb}
578601
onNodeMouseOut={this.handleOnNodeMouseOutCb}
602+
handleAddChildrenToNode={this.handleAddChildrenToNode}
579603
subscriptions={subscriptions}
580604
centerNode={this.centerNode}
581605
/>

src/Tree/types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,4 +310,13 @@ export interface TreeProps {
310310
* {@link Tree.defaultProps.hasInteractiveNodes | Default value}
311311
*/
312312
hasInteractiveNodes?: boolean;
313+
314+
/**
315+
* Indicates the tree being represented by the data. If the dataKey changes, then we should re-render the tree.
316+
* If the data changes but the dataKey keeps being the same, then it's a change (like adding children to a node) for the same tree,
317+
* so we shouldn't re-render the tree.
318+
*
319+
* {@link Tree.defaultProps.dataKey | Default value}
320+
*/
321+
dataKey?: string;
313322
}

0 commit comments

Comments
 (0)