Skip to content

Commit 6dac21a

Browse files
committed
fix: recompute tree when props change
1 parent 9e40f3d commit 6dac21a

File tree

5 files changed

+221
-206
lines changed

5 files changed

+221
-206
lines changed

__tests__/FixedSizeTree.spec.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ describe('FixedSizeTree', () => {
294294
});
295295

296296
it('resets current openness to default', async () => {
297-
const {records} = component.state();
297+
const records = component.state('records');
298298

299299
for (const id in records) {
300300
records[id].isOpen = false;
@@ -399,12 +399,12 @@ describe('FixedSizeTree', () => {
399399
});
400400

401401
it('provides a toggle function that changes openness state of the specific node', async () => {
402-
const recomputeTreeSpy = spyOn(treeInstance, 'recomputeTree');
403-
const foo1 = component.state().records['foo-1'];
402+
const foo1 = component.state('records')['foo-1'];
404403

404+
treeWalkerSpy.mockClear();
405405
await foo1.toggle();
406406

407-
expect(recomputeTreeSpy).toHaveBeenCalledWith({refreshNodes: false});
407+
expect(treeWalkerSpy).toHaveBeenCalledWith(false);
408408
expect(foo1.isOpen).toBeFalsy();
409409
});
410410
});

__tests__/VariableSizeTree.spec.tsx

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -447,26 +447,28 @@ describe('VariableSizeTree', () => {
447447
});
448448

449449
it('provides a toggle function that changes openness state of the specific node', async () => {
450-
const recomputeTreeSpy = spyOn(treeInstance, 'recomputeTree');
451-
const foo1 = component.state().records['foo-1'];
450+
const foo1 = component.state('records')['foo-1'];
452451

452+
foo1.height = 50;
453+
454+
treeWalkerSpy.mockClear();
453455
await foo1.toggle();
454456

455-
expect(recomputeTreeSpy).toHaveBeenCalledWith({
456-
refreshNodes: false,
457-
useDefaultHeight: true,
458-
});
457+
expect(treeWalkerSpy).toHaveBeenCalledWith(false);
458+
expect(foo1.height).toBe(defaultHeight);
459459
expect(foo1.isOpen).toBeFalsy();
460460
});
461461

462462
it('resets current height to default', async () => {
463+
const records = component.state('records');
464+
463465
// Imitate changing height for the foo-1 node
464466
component.setState({
465467
order: ['foo-1'],
466468
records: {
467-
...component.state().records,
469+
...records,
468470
'foo-1': {
469-
...component.state().records['foo-1'],
471+
...records['foo-1'],
470472
height: 60,
471473
},
472474
},
@@ -581,12 +583,20 @@ describe('VariableSizeTree', () => {
581583
});
582584

583585
it('provides a resize function that changes height of the specific node', () => {
584-
const resetAfterIdSpy = spyOn(treeInstance, 'resetAfterId');
585-
const foo3 = component.state().records['foo-3'];
586+
const listInstance: VariableSizeList = component
587+
.find(VariableSizeList)
588+
.instance() as VariableSizeList;
589+
590+
const resetAfterIndexSpy = spyOn(listInstance, 'resetAfterIndex');
591+
const order = component.state('order');
592+
const foo3 = component.state('records')['foo-3'];
586593

587594
foo3.resize(100, true);
588595

589-
expect(resetAfterIdSpy).toHaveBeenCalledWith('foo-3', true);
596+
expect(resetAfterIndexSpy).toHaveBeenCalledWith(
597+
order.indexOf('foo-3'),
598+
true,
599+
);
590600
expect(foo3.height).toBe(100);
591601
});
592602
});

src/FixedSizeTree.tsx

Lines changed: 80 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,79 @@ export type FixedSizeTreeState<T> = TreeState<
2727
FixedSizeNodeComponentProps<T>,
2828
FixedSizeNodeRecord<T>,
2929
FixedSizeNodeData<T>,
30+
FixedSizeUpdateOptions,
3031
T
3132
>;
3233

34+
const computeTree = <T extends {}>(
35+
{
36+
refreshNodes = false,
37+
useDefaultOpenness = false,
38+
}: FixedSizeUpdateOptions = {},
39+
{treeWalker}: FixedSizeTreeProps<T>,
40+
{records: prevRecords, recomputeTree}: FixedSizeTreeState<T>,
41+
): Pick<FixedSizeTreeState<T>, 'order' | 'records'> => {
42+
const order: Array<string | symbol> = [];
43+
const records = {...prevRecords};
44+
const iter = treeWalker(refreshNodes);
45+
46+
if (useDefaultOpenness) {
47+
for (const id in records) {
48+
records[id].isOpen = records[id].data.isOpenByDefault;
49+
}
50+
}
51+
52+
let isPreviousOpened = false;
53+
54+
while (true) {
55+
const {done, value} = iter.next(isPreviousOpened);
56+
57+
if (done || !value) {
58+
break;
59+
}
60+
61+
let id: string | symbol;
62+
63+
if (typeof value === 'string' || typeof value === 'symbol') {
64+
id = value;
65+
66+
if (useDefaultOpenness) {
67+
records[id as string].isOpen =
68+
records[id as string].data.isOpenByDefault;
69+
}
70+
} else {
71+
({id} = value);
72+
const {isOpenByDefault} = value;
73+
const record = records[id as string];
74+
75+
if (!record) {
76+
records[id as string] = {
77+
data: value,
78+
isOpen: isOpenByDefault,
79+
async toggle(this: FixedSizeNodeRecord<T>): Promise<void> {
80+
this.isOpen = !this.isOpen;
81+
await recomputeTree({refreshNodes: this.isOpen});
82+
},
83+
};
84+
} else {
85+
record.data = value;
86+
87+
if (useDefaultOpenness) {
88+
record.isOpen = isOpenByDefault;
89+
}
90+
}
91+
}
92+
93+
order.push(id);
94+
isPreviousOpened = records[id as string].isOpen;
95+
}
96+
97+
return {
98+
order,
99+
records,
100+
};
101+
};
102+
33103
export default class FixedSizeTree<T> extends React.PureComponent<
34104
FixedSizeTreeProps<T>,
35105
FixedSizeTreeState<T>
@@ -38,13 +108,16 @@ export default class FixedSizeTree<T> extends React.PureComponent<
38108
rowComponent: Row,
39109
};
40110

41-
public static getDerivedStateFromProps({
42-
children: component,
43-
itemData: treeData,
44-
}: FixedSizeTreeProps<{}>): Partial<FixedSizeTreeState<{}>> {
111+
public static getDerivedStateFromProps(
112+
props: FixedSizeTreeProps<{}>,
113+
state: FixedSizeTreeState<{}>,
114+
): Partial<FixedSizeTreeState<{}>> {
115+
const {children: component, itemData: treeData} = props;
116+
45117
return {
46118
component,
47119
treeData,
120+
...computeTree({refreshNodes: true}, props, state),
48121
};
49122
}
50123

@@ -53,24 +126,23 @@ export default class FixedSizeTree<T> extends React.PureComponent<
53126
public constructor(props: FixedSizeTreeProps<T>, context: any) {
54127
super(props, context);
55128

56-
this.createNodeRecord = this.createNodeRecord.bind(this);
57-
58129
const initialState: FixedSizeTreeState<T> = {
59130
component: props.children,
60131
order: [],
132+
recomputeTree: this.recomputeTree.bind(this),
61133
records: {},
62134
};
63135

64136
this.state = {
65137
...initialState,
66-
...this.computeTree({refreshNodes: true}, props, initialState),
138+
...computeTree({refreshNodes: true}, props, initialState),
67139
};
68140
}
69141

70142
public async recomputeTree(options?: FixedSizeUpdateOptions): Promise<void> {
71143
return new Promise(resolve => {
72144
this.setState<never>(
73-
prevState => this.computeTree(options, this.props, prevState),
145+
prevState => computeTree(options, this.props, prevState),
74146
resolve,
75147
);
76148
});
@@ -98,79 +170,4 @@ export default class FixedSizeTree<T> extends React.PureComponent<
98170
</FixedSizeList>
99171
);
100172
}
101-
102-
private computeTree(
103-
{
104-
refreshNodes = false,
105-
useDefaultOpenness = false,
106-
}: FixedSizeUpdateOptions = {},
107-
{treeWalker}: FixedSizeTreeProps<T>,
108-
{records: prevRecords}: FixedSizeTreeState<T>,
109-
): Pick<FixedSizeTreeState<T>, 'order' | 'records'> {
110-
const order: Array<string | symbol> = [];
111-
const records = {...prevRecords};
112-
const iter = treeWalker(refreshNodes);
113-
114-
if (useDefaultOpenness) {
115-
for (const id in records) {
116-
records[id].isOpen = records[id].data.isOpenByDefault;
117-
}
118-
}
119-
120-
let isPreviousOpened = false;
121-
122-
while (true) {
123-
const {done, value} = iter.next(isPreviousOpened);
124-
125-
if (done || !value) {
126-
break;
127-
}
128-
129-
let id: string | symbol;
130-
131-
if (typeof value === 'string' || typeof value === 'symbol') {
132-
id = value;
133-
134-
if (useDefaultOpenness) {
135-
records[id as string].isOpen =
136-
records[id as string].data.isOpenByDefault;
137-
}
138-
} else {
139-
({id} = value);
140-
const {isOpenByDefault} = value;
141-
const record = records[id as string];
142-
143-
if (!record) {
144-
records[id as string] = this.createNodeRecord(value);
145-
} else {
146-
record.data = value;
147-
148-
if (useDefaultOpenness) {
149-
record.isOpen = isOpenByDefault;
150-
}
151-
}
152-
}
153-
154-
order.push(id);
155-
isPreviousOpened = records[id as string].isOpen;
156-
}
157-
158-
return {
159-
order,
160-
records,
161-
};
162-
}
163-
164-
private createNodeRecord(data: FixedSizeNodeData<T>): FixedSizeNodeRecord<T> {
165-
const record: FixedSizeNodeRecord<T> = {
166-
data,
167-
isOpen: data.isOpenByDefault,
168-
toggle: async () => {
169-
record.isOpen = !record.isOpen;
170-
await this.recomputeTree({refreshNodes: record.isOpen});
171-
},
172-
};
173-
174-
return record;
175-
}
176173
}

0 commit comments

Comments
 (0)