Skip to content

Commit cfcfd18

Browse files
committed
fix(tree@v1): ensures initialDepth is reapplied if dataset changes (#47)
1 parent eb6a094 commit cfcfd18

File tree

3 files changed

+79
-18
lines changed

3 files changed

+79
-18
lines changed

src/Tree/index.js

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ class Tree extends React.Component {
1919
d3: Tree.calculateD3Geometry(this.props),
2020
rd3tSvgClassName: `_${uuid.v4()}`,
2121
rd3tGClassName: `_${uuid.v4()}`,
22+
isInitialRenderForDataset: true,
2223
};
2324

2425
internalState = {
25-
initialRender: true,
2626
targetNode: null,
2727
isTransitioning: false,
2828
};
@@ -36,6 +36,7 @@ class Tree extends React.Component {
3636
// eslint-disable-next-line react/no-unused-state
3737
dataRef: nextProps.data,
3838
data: Tree.assignInternalProperties(clone(nextProps.data)),
39+
isInitialRenderForDataset: true,
3940
};
4041
}
4142

@@ -50,10 +51,17 @@ class Tree extends React.Component {
5051

5152
componentDidMount() {
5253
this.bindZoomListener(this.props);
53-
this.internalState.initialRender = false;
54+
// eslint-disable-next-line
55+
this.setState({ isInitialRenderForDataset: false });
5456
}
5557

5658
componentDidUpdate(prevProps) {
59+
if (this.props.data !== prevProps.data) {
60+
// If last `render` was due to change in dataset -> mark the initial render as done.
61+
// eslint-disable-next-line
62+
this.setState({ isInitialRenderForDataset: false });
63+
}
64+
5765
// If zoom-specific props change -> rebind listener with new values
5866
// Or: rebind zoom listeners to new DOM nodes in case NodeWrapper switched <TransitionGroup> <-> <g>
5967
if (
@@ -423,24 +431,29 @@ class Tree extends React.Component {
423431
nodeSize,
424432
orientation,
425433
} = this.props;
434+
const { isInitialRenderForDataset } = this.state;
426435

427436
const tree = layout
428437
.tree()
429438
.nodeSize(orientation === 'horizontal' ? [nodeSize.y, nodeSize.x] : [nodeSize.x, nodeSize.y])
430439
.separation(
431440
(a, b) => (a.parent.id === b.parent.id ? separation.siblings : separation.nonSiblings),
432441
)
433-
.children(d => (d._collapsed ? null : d._children));
442+
.children(d => {
443+
if (initialDepth !== undefined && isInitialRenderForDataset) {
444+
// If `initialDepth` is defined, a node's depth determines
445+
// whether we append `children` on the first render.
446+
return d.depth >= initialDepth ? null : d._children;
447+
}
448+
// Node's `collapsed` property determines appending of `children` for subsequent renders.
449+
return d._collapsed ? null : d._children;
450+
});
434451

435452
const rootNode = this.state.data[0];
436453
let nodes = tree.nodes(rootNode);
437454

438455
// set `initialDepth` on first render if specified
439-
if (
440-
useCollapseData === false &&
441-
initialDepth !== undefined &&
442-
this.internalState.initialRender
443-
) {
456+
if (useCollapseData === false && initialDepth !== undefined && isInitialRenderForDataset) {
444457
this.setInitialTreeDepth(nodes, initialDepth);
445458
nodes = tree.nodes(rootNode);
446459
}

src/Tree/tests/index.test.js

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
/* eslint-disable camelcase */
2+
13
import React from 'react';
24
import { shallow, mount } from 'enzyme';
35
import { render } from 'react-dom';
@@ -6,7 +8,7 @@ import NodeWrapper from '../NodeWrapper';
68
import Node from '../../Node';
79
import Link from '../../Link';
810
import Tree from '../index';
9-
import { mockData, mockData2, mockData3, mockData4 } from './mockData';
11+
import { mockData, mockData2, mockData3, mockData4, mockTree_D1N2_D2N2 } from './mockData';
1012

1113
describe('<Tree />', () => {
1214
jest.spyOn(Tree.prototype, 'generateTree');
@@ -270,14 +272,19 @@ describe('<Tree />', () => {
270272
});
271273

272274
describe('initialDepth', () => {
273-
it('sets tree depth to `props.initialDepth` if specified', () => {
274-
mount(<Tree data={mockData} initialDepth={1} />);
275-
expect(Tree.prototype.setInitialTreeDepth).toHaveBeenCalled();
275+
it('expands tree to full depth by default', () => {
276+
const renderedComponent = shallow(<Tree data={mockTree_D1N2_D2N2} />);
277+
expect(renderedComponent.find(Node).length).toBe(5);
278+
});
279+
280+
it('expands tree to `props.initialDepth` if specified', () => {
281+
const renderedComponent = shallow(<Tree data={mockTree_D1N2_D2N2} initialDepth={1} />);
282+
expect(renderedComponent.find(Node).length).toBe(3);
276283
});
277284

278-
it('does not set an initialDepth if `props.useCollapseData` is true', () => {
279-
mount(<Tree data={mockData} initialDepth={1} useCollapseData />);
280-
expect(Tree.prototype.setInitialTreeDepth).not.toHaveBeenCalled();
285+
it('renders only the root node if `initialDepth === 0`', () => {
286+
const renderedComponent = shallow(<Tree data={mockTree_D1N2_D2N2} initialDepth={0} />);
287+
expect(renderedComponent.find(Node).length).toBe(1);
281288
});
282289
});
283290

@@ -746,7 +753,6 @@ describe('<Tree />', () => {
746753
.first()
747754
.simulate('click'); // collapse
748755

749-
expect(onUpdateSpy).toHaveBeenCalledTimes(1);
750756
expect(onUpdateSpy).toHaveBeenCalledWith({
751757
node: expect.any(Object),
752758
zoom: 1,
@@ -764,7 +770,6 @@ describe('<Tree />', () => {
764770
);
765771
const scrollableComponent = document.querySelector('.rd3t-tree-container > svg');
766772
scrollableComponent.dispatchEvent(new Event('wheel'));
767-
expect(onUpdateSpy).toHaveBeenCalledTimes(1);
768773
expect(onUpdateSpy).toHaveBeenCalledWith({
769774
node: null,
770775
translate: { x: expect.any(Number), y: expect.any(Number) },
@@ -798,7 +803,6 @@ describe('<Tree />', () => {
798803
.first()
799804
.simulate('click');
800805

801-
expect(onUpdateSpy).toHaveBeenCalledTimes(1);
802806
expect(onUpdateSpy).toHaveBeenCalledWith({
803807
node: expect.any(Object),
804808
translate,

src/Tree/tests/mockData.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,47 @@
1+
/* eslint-disable camelcase */
2+
3+
export const mockTree_D1N2_D2N2 = [
4+
{
5+
name: 'Top Level',
6+
attributes: {
7+
keyA: 'val A',
8+
keyB: 'val B',
9+
keyC: 'val C',
10+
},
11+
children: [
12+
{
13+
name: 'Level 2: A',
14+
attributes: {
15+
keyA: 'val A',
16+
keyB: 'val B',
17+
keyC: 'val C',
18+
},
19+
children: [
20+
{
21+
name: '3: Son of A',
22+
attributes: {
23+
keyA: 'val A',
24+
keyB: 'val B',
25+
keyC: 'val C',
26+
},
27+
},
28+
{
29+
name: '3: Daughter of A',
30+
attributes: {
31+
keyA: 'val A',
32+
keyB: 'val B',
33+
keyC: 'val C',
34+
},
35+
},
36+
],
37+
},
38+
{
39+
name: 'Level 2: B',
40+
},
41+
],
42+
},
43+
];
44+
145
const mockData = [
246
{
347
name: 'Top Level',

0 commit comments

Comments
 (0)