Skip to content

Commit d951f2d

Browse files
committed
docs: add migration guide
1 parent 6d68b31 commit d951f2d

File tree

1 file changed

+171
-26
lines changed

1 file changed

+171
-26
lines changed

README.md

Lines changed: 171 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -38,25 +38,31 @@ import {FixedSizeTree as Tree} from 'react-vtree';
3838
// Tree component can work with any possible tree structure because it uses an
3939
// iterator function that the user provides. Structure, approach, and iterator
4040
// function below is just one of many possible variants.
41-
const tree = {
42-
name: 'Root #1',
43-
id: 'root-1',
44-
children: [
45-
{
46-
children: [
47-
{id: 'child-2', name: 'Child #2'},
48-
{id: 'child-3', name: 'Child #3'},
49-
],
50-
id: 'child-1',
51-
name: 'Child #1',
52-
},
53-
{
54-
children: [{id: 'child-5', name: 'Child #5'}],
55-
id: 'child-4',
56-
name: 'Child #4',
57-
},
58-
],
59-
};
41+
const treeNodes = [
42+
{
43+
name: 'Root #1',
44+
id: 'root-1',
45+
children: [
46+
{
47+
children: [
48+
{id: 'child-2', name: 'Child #2'},
49+
{id: 'child-3', name: 'Child #3'},
50+
],
51+
id: 'child-1',
52+
name: 'Child #1',
53+
},
54+
{
55+
children: [{id: 'child-5', name: 'Child #5'}],
56+
id: 'child-4',
57+
name: 'Child #4',
58+
},
59+
],
60+
},
61+
{
62+
name: 'Root #2',
63+
id: 'root-2',
64+
},
65+
];
6066

6167
// This helper function constructs the object that will be sent back at the step
6268
// [2] during the treeWalker function work. Except for the mandatory `data`
@@ -76,8 +82,11 @@ const getNodeData = (node, nestingLevel) => ({
7682
// The `treeWalker` function runs only on tree re-build which is performed
7783
// whenever the `treeWalker` prop is changed.
7884
function* treeWalker() {
79-
// Step [1]: Define the root node of our tree.
80-
yield getNodeData(rootNode, 0);
85+
// Step [1]: Define the root node of our tree. There can be one or
86+
// multiple nodes.
87+
for (let i = 0; i < treeNodes.length; i++) {
88+
yield getNodeData(treeNodes[i], 0);
89+
}
8190

8291
while (true) {
8392
// Step [2]: Get the parent component back. It will be the object
@@ -230,7 +239,7 @@ The rules object has the following shape:
230239
- `open: boolean` - this rule changes the openness state for the owner node only (subtree nodes are not affected).
231240
- `subtreeCallback(node: object, ownerNode: object): void` - this callback runs against each node in the subtree of the owner node (including the owner node as well). It receives the subtree node and the owner node. Changing any property of the subtree node will affect the node state and how it will be displayed (e.g. if you change the node openness state it will be displayed according to the changed state).
232241

233-
The order of rules matters. If you specify the child node rules before the parent node rules, and that rules affect the same property, the parent node subtreeWalker will override that property. So if you want to override parent's rules, place children rules after the parent's.
242+
The order of rules matters. If you specify the child node rules before the parent node rules, and that rules affect the same property, the parent node `subtreeWalker` will override that property. So if you want to override parent's rules, place children rules after the parent's.
234243

235244
The type of the node objects received by `subtreeCallback` is `FixedSizeNodePublicState`. See the [types description](#types) below.
236245

@@ -344,8 +353,9 @@ const getNodeData = (node, nestingLevel) => ({
344353
// The `treeWalker` function runs only on tree re-build which is performed
345354
// whenever the `treeWalker` prop is changed.
346355
function* treeWalker() {
347-
// Step [1]: Define the root node of our tree.
348-
yield getNodeData(rootNode, 0);
356+
// Step [1]: Define the root node of our tree. There can be yielded one or
357+
// multiple nodes.
358+
yield getNodeData(tree, 0);
349359

350360
while (true) {
351361
// Step [2]: Get the parent component back. It will be the object
@@ -436,9 +446,9 @@ All types in this section are the extended variants of [`FixedSizeTree` types](#
436446

437447
The `treeWalker` algorithm works in the following way. During the execution, the `treeWalker` function sends a bunch of objects to the tree component which builds an internal representation of the tree. However, for it, the specific order of yieldings should be performed.
438448

439-
1. The first yielding is always the root node. It will be the foundation of the whole tree.
449+
1. The first yielding is always root nodes. They will be the foundation of the whole tree.
440450
2. Now start a loop where you will receive the parent node and yield all the children of it.
441-
3. The first yielding of loop iteration should yield an `undefined`. In exchange, you will receive a node for which you should yield all the children in the same way you've done with the root.
451+
3. The first yielding of loop iteration should yield an `undefined`. In exchange, you will receive a node for which you should yield all the children in the same way you've done with the root ones.
442452
4. When all the children are yielded, and the new iteration of loop is started, you yield `undefined` again and in exchange receive the next node. It may be:
443453
- a child node if the previous node has children;
444454
- a sibling node if it has siblings;
@@ -467,3 +477,138 @@ function* treeWalker() {
467477
}
468478
}
469479
```
480+
481+
## Migrating v2 -> v3
482+
483+
If you use `react-vtree` of version 2, it is preferable migrate to the version 3. The third version is quite different under the hood and provides way more optimized approach to the initial tree building and tree openness state change. The most obvious it becomes if you have a giant tree (with about 1 million of nodes).
484+
485+
To migrate to the new version, you have to do the following steps.
486+
487+
### 1. Migrate `treeWalker`
488+
489+
The `treeWalker` was and is the heart of the `react-vtree`. However, now it looks a bit different.
490+
491+
Old `treeWalker` worked for both initial tree building and changing node openness state:
492+
493+
```js
494+
function* treeWalker( refresh
495+
) {
496+
const stack = [];
497+
498+
stack.push({
499+
nestingLevel: 0,
500+
node: rootNode,
501+
});
502+
503+
// Go through all the nodes adding children to the stack and removing them
504+
// when they are processed.
505+
while (stack.length !== 0) {
506+
const {node, nestingLevel} = stack.pop();
507+
const id = node.id.toString();
508+
509+
// Receive the openness state of the node we are working with
510+
const isOpened = yield refresh
511+
? {
512+
id,
513+
isLeaf: node.children.length === 0,
514+
isOpenByDefault: true,
515+
name: node.name,
516+
nestingLevel,
517+
}
518+
: id;
519+
520+
if (node.children.length !== 0 && isOpened) {
521+
for (let i = node.children.length - 1; i >= 0; i--) {
522+
stack.push({
523+
nestingLevel: nestingLevel + 1,
524+
node: node.children[i],
525+
});
526+
}
527+
}
528+
}
529+
}
530+
```
531+
532+
The new `treeWalker` is only for the tree building. The `Tree` component builds and preserves the tree structure internally. See the full description [above](#treewalker-algorithm).
533+
534+
```js
535+
// This function prepares an object for yielding. We can yield an object
536+
// that has `data` object with `id` and `isOpenByDefault` fields.
537+
// We can also add any other data here.
538+
const getNodeData = (node, nestingLevel) => ({
539+
data: {
540+
id: node.id.toString(),
541+
isLeaf: node.children.length === 0,
542+
isOpenByDefault: true,
543+
name: node.name,
544+
nestingLevel,
545+
},
546+
nestingLevel,
547+
node,
548+
});
549+
550+
function* treeWalker() {
551+
// Here we send root nodes to the component.
552+
for (let i = 0; i < rootNodes.length; i++) {
553+
yield getNodeData(rootNodes[i], 0);
554+
}
555+
556+
while (true) {
557+
// Here we receive an object we created via getNodeData function
558+
// and yielded before. All we need here is to describe its children
559+
// in the same way we described the root nodes.
560+
const parentMeta = yield;
561+
562+
for (let i = 0; i < parentMeta.node.children.length; i++) {
563+
yield getNodeData(
564+
parentMeta.node.children[i],
565+
parentMeta.nestingLevel + 1,
566+
);
567+
}
568+
}
569+
}
570+
```
571+
572+
### 2. Migrate tree components
573+
574+
Components haven't been changed a lot but you may want to add new features like:
575+
576+
- [`async`](#async-boolean)
577+
- [`placeholder`](#placeholder-reactnode--null)
578+
- [`buildingTaskTimeout`](#buildingtasktimeout-number).
579+
580+
### 3. Migrate `recomputeTree` method
581+
582+
The `recomputeTree` method now receives a list of nodes to change (previously, it was an `opennessState` object). See the full description [above](#async-recomputetreestate-void).
583+
584+
The most important change is the introduction of the `subtreeCallback`. It is a function that will be applied to each node in the subtree of the specified node. Among other useful things it also allows imitating the behavior of old `useDefaultOpenness` and `useDefaultHeight` options.
585+
586+
Old `recomputeTree`:
587+
```js
588+
treeInstance.recomputeTree({
589+
opennessState: {
590+
'node-1': true,
591+
'node-2': true,
592+
'node-3': false,
593+
},
594+
refreshNodes: true,
595+
useDefaultOpenness: false
596+
});
597+
```
598+
599+
New `recomputeTree`:
600+
601+
```js
602+
treeInstance.recomputeTree({
603+
'node-1': true,
604+
'node-2': {
605+
open: true,
606+
subtreeCallback(node, ownerNode) {
607+
if (node !== ownerNode) {
608+
node.isOpen = false;
609+
}
610+
}
611+
},
612+
'node-3': false,
613+
});
614+
```

0 commit comments

Comments
 (0)