Skip to content

Releases: Lodin/react-vtree

3.0.0-beta.0

06 Dec 13:37
Compare
Choose a tag to compare
3.0.0-beta.0 Pre-release
Pre-release

This release contains the significant number of changes of the library. The main theme of the release is to increase the speed of rendering giant trees (trees with ~1 million nodes) which required the re-writing of the tree building and tree updating algorithms.

Breaking changes

treeWalker

The treeWalker function has the completely different shape now.

Old treeWalker worked for both initial tree building and changing node openness state:

Old treeWalker
function* treeWalker(  refresh
) {
  const stack = [];

  stack.push({
    nestingLevel: 0,
    node: rootNode,
  });

  // Go through all the nodes adding children to the stack and removing them
  // when they are processed.
  while (stack.length !== 0) {
    const {node, nestingLevel} = stack.pop();
    const id = node.id.toString();

    // Receive the openness state of the node we are working with
    const isOpened = yield refresh
      ? {
          id,
          isLeaf: node.children.length === 0,
          isOpenByDefault: true,
          name: node.name,
          nestingLevel,
        }
      : id;

    if (node.children.length !== 0 && isOpened) {
      for (let i = node.children.length - 1; i >= 0; i--) {
        stack.push({
          nestingLevel: nestingLevel + 1,
          node: node.children[i],
        });
      }
    }
  }
}

The new treeWalker is only for the tree building. The Tree component builds and preserves the tree structure internally.

New treeWalker
// This function prepares an object for yielding. We can yield an object
// that has `data` object with `id` and `isOpenByDefault` fields.
// We can also add any other data here.
const getNodeData = (node, nestingLevel) => ({
  data: {
    id: node.id.toString(),
    isLeaf: node.children.length === 0,
    isOpenByDefault: true,
    name: node.name,
    nestingLevel,
  },
  nestingLevel,
  node,
});

function* treeWalker() {
  // Here we send root nodes to the component.
  for (let i = 0; i < rootNodes.length; i++) {
    yield getNodeData(rootNodes[i], 0);
  }

  while (true) {
    // Here we receive an object we created via getNodeData function
    // and yielded before. All we need here is to describe its children
    // in the same way we described the root nodes.
    const parentMeta = yield;

    for (let i = 0; i < parentMeta.node.children.length; i++) {
      yield getNodeData(
        parentMeta.node.children[i],
        parentMeta.nestingLevel + 1,
      );
    }
  }
}

recomputeTree

The recomputeTree method has been completely re-worked. Now it is way more powerful than its predecessor, and the main role in this change is played by subtreeCallback function that applies to all descendants of the selected node. With it, you can do a lot of things including emulation of the old useDefaultOpenness and useDefaultHeight options.

Old recomputeTree
treeInstance.recomputeTree({
  opennessState: {
    'node-1': true,
    'node-2': true,
    'node-3': false,
  },
  refreshNodes: true,
  useDefaultOpenness: false
});
New recomputeTree
treeInstance.recomputeTree({
  'node-1': true,
  'node-2': {
    open: true,
    subtreeCallback(node, ownerNode) {
      if (node !== ownerNode) {
        node.isOpen = false;
      }
    }
  },
  'node-3': false,
});

toggle -> setOpen

The toggle function sent to the Node component is replaced with setOpen function. setOpen function has the following signature and allows specifying the node openness state directly without relying on the internal state.

function setOpen(state: boolean): void;
toggle
const Node = ({
  data: {isLeaf, name, nestingLevel},
  isOpen,
  style,
  toggle,
}) => {
  <div
    style={{
      ...style,
      marginLeft: nestingLevel * 30 + (isLeaf ? 48 : 0),
    }}
  >
    {!isLeaf && (
      <div>
        // Here the `toggle` function is used
        <button type="button" onClick={toggle>
          {isOpen ? '-' : '+'}
        </button>
      </div>
    )}
    <div>{name}</div>
  </div>
}
setOpen
const Node = ({
  data: {isLeaf, name, nestingLevel},
  isOpen,
  style,
  setOpen,
}) => {
  <div
    style={{
      ...style,
      marginLeft: nestingLevel * 30 + (isLeaf ? 48 : 0),
    }}
  >
    {!isLeaf && (
      <div>
        // emulating the `toggle` function
        <button type="button" onClick={() => setOpen(!isOpen)>
          {isOpen ? '-' : '+'}
        </button>
      </div>
    )}
    <div>{name}</div>
  </div>
}

Node id can be only string now

Since we have a tree that reflected is reflected as a virtual list, it is a good idea to use node ids as React keys to speed up the React re-rendering on toggling the nodes. It would allow re-rendering only the required elements instead of re-rendering whole tree. However, it also forbids using symbol as id because React do not support symbols as keys.

Features

New props

async: boolean

This option allows making the tree asynchronous; e.g. you will be able to load the branch data on the node opening. All it does under the hood is preserving the tree state between tree buildings on treeWalker update, so the user does not see the tree resetting to the default state when the async action is performed.

If it is combined with the placeholder option, the tree re-building won't be interrupted by showing the placeholder; it will be shown only at the first time the tree is building.

placeholder: ReactNode | null

This property receives any react node that will be displayed instead of a tree during the building process. This option should only be used if the tree building process requires too much time (which means you have a really giant amount of data, e.g. about a million nodes).

Setting this option enables the requestIdleCallback under the hood for browsers that support this feature. For other browsers the original scenario is applied; no placeholder will be shown.

Using this feature allows avoiding UI freezes; however, it may slightly increase the time spent for the building process.

If you have an asynchronous giant tree and want to use profits of requestIdleCallback but don't want placeholder to be shown on the first render (that is probably quite small because all other data will be loaded asynchronously), set placeholder to null. No placeholder will be shown on the first render but the requestIdleCallback building will be enabled and allow avoiding freezes on tree re-building when tree becomes bigger.

buildingTaskTimeout: number

This option works in tandem with the placeholder option. With it, you can set the task timeout for the requestIdleCallback. The buildingTaskTimeout will be sent directly as the requestIdleCallback's timeout option.

listRef: Ref<FixedSizeList>

This option allows you to get the instance of the internal react-window list. It is usually unnecessary because all necessary methods are already provided but still can be useful for edge cases.

Migrating 2.x.x -> 3.x.x

Also described in README.

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).

To migrate to the new version, you have to do the following steps.

1. Migrate treeWalker

The treeWalker was and is the heart of the react-vtree. However, now it looks a bit different.

Old treeWalker worked for both initial tree building and changing node openness state:

function* treeWalker(refresh) {
  const stack = [];

  stack.push({
    nestingLevel: 0,
    node: rootNode,
  });

  // Go through all the nodes adding children to the stack and removing them
  // when they are processed.
  while (stack.length !== 0) {
    const {node, nestingLevel} = stack.pop();
    const id = node.id.toString();

    // Receive the openness state of the node we are working with
    const isOpened = yield refresh
      ? {
          id,
          isLeaf: node.children.length === 0,
          isOpenByDefault: true,
          name: node.name,
          nestingLevel,
        }
      : id;

    if (node.children.length !== 0 && isOpened) {
      for (let i = node.children.length - 1; i >= 0; i--) {
        stack.push({
          nestingLevel: nestingLevel + 1,
          node: node.children[i],
        });
      }
    }
  }
}

The new treeWalker is only for the tree building. The Tree component builds and preserves the tree structure internally. See the full description above.

// This function prepares an object for yielding. We can yield an object
// that has `data` object with `id` and `isOpenByDefault` fields.
// We can also add any other data here.
const getNodeData = (node, nestingLevel) => ({
  data: {
    id: node.id.toString(),
    isLeaf: node.children.length === 0,
    isOpenByDefault: true,
    name: node.name,
    nestingLevel,
  },
  nestingLevel,
  node,
});

function* treeWalker() {
  // Here we send root nodes to the component.
  for (let i = 0; i < rootNodes.length; i++) {
    yield getNodeData(rootNodes[i], 0);
  }

 ...
Read more

2.0.4

06 Dec 13:53
Compare
Choose a tag to compare

Bugfix release

Changes

  • Remember a new treeWalker provided to the component (#38 by @Lodin).

2.0.3

11 Oct 18:06
Compare
Choose a tag to compare

The release contains a fix for #35.

Changes

  • Consider opennessState on record creation (#36 by @Lodin).

v2.0.2

10 Aug 15:40
Compare
Choose a tag to compare

The release contains the additional fix for #25.

Changes

  • Fix resetting height after re-computation (second attempt) (#28 by @Lodin).

v2.0.1

09 Aug 15:59
Compare
Choose a tag to compare

This release contains a fix for #25

Changes

  • Fix resetting height after re-computation (#26 by @Lodin).

v2.0.0

02 Aug 20:22
Compare
Choose a tag to compare

This release adds a new feature to open or close nodes programmatically along with moving to the GitHub Actions/SonarCloud and refactoring that reduces the size of the package.

Open and close nodes programmatically

Now there is an ability to change the node openness state programmatically. To do it, you have to call the recomputeTree method with a new opennessState parameter. You have to specify an object that contains IDs of nodes you want to toggle as keys and their new openness state as values.

treeRef.current.recomputeTree({
  opennessState: {
    foo: true,
    bar: false,
  }
});

NOTE: If you specify both useDefaultOpenness and opennessState, opennessState will be overridden by useDefaultOpenness results.

Moving to SonarClound and GitHub Actions

Now the project uses GitHub Actions for CI along with SonarCloud for analyzing test coverage.

Reducing the package size

This release contains quite a big refactoring that deduplicates the common code for FixedSizeTree and VariableSizeTree. It also changes Typescript types a lot which is considered breaking changes and makes this release major.

Changes

  • Update README.md (#22 by @johannalee)
  • Allow updating node openness programmatically + global refactoring and dependecy updates (#23 by @Lodin).
  • Use GitHub actions and SonarCloud for testing and analysis (#24 by @Lodin).

v1.0.2

30 Jan 08:45
Compare
Choose a tag to compare

Small patch release. Fixes a regression when toggle function loses context.

Changes

  • Fix context regression in toggle function (#15 by @Lodin).

v1.0.1

26 Jan 15:30
Compare
Choose a tag to compare

Small patch release. Fixes a bug when Tree component did not recompute all the nodes on new treeWalker property received.

Changes

  • Recompute on new treeWalker (#13 by @Lodin).

v1.0.0

04 Dec 16:09
Compare
Choose a tag to compare

This release moves the library out of beta. It does not bring a lot of changes, and exists mostly for the version change.

Changes

  • Allow calling recomputeTree without arguments (#8 by @Lodin).

v1.0.0-beta.0

13 Aug 22:49
Compare
Choose a tag to compare
v1.0.0-beta.0 Pre-release
Pre-release

This release introduces a significant change to the library by replacing the library this package based on from react-virtualized to react-window.

Reason

The react-window is a new virtualization library for React from the same author that is going to replace old and awkward react-virtualized. The components from the new library have a user-friendly design and bring way fewer bytes than the old one.

Breaking changes

The whole release is a breaking change. It would be correct to say that it is an entirely new library based on old ideas. To migrate from the v0.1.7 version to the 1.0.0-beta.0 it would be better to follow the examples and storybook stories and rewrite your components that uses the original tree component. Also, you can follow the recommendations from #5.

Changes

  • Rework library to use react-window (#5 by @Lodin)