UNPKG

react-vtree

Version:

React component for efficiently rendering large tree structures

329 lines (248 loc) 13 kB
# react-vtree [![Latest Stable Version](https://img.shields.io/npm/v/react-vtree.svg)](https://www.npmjs.com/package/react-vtree) [![License](https://img.shields.io/npm/l/react-vtree.svg)](./LICENSE) [![CI Status](https://github.com/Lodin/react-vtree/workflows/CI/badge.svg)](https://github.com/Lodin/react-vtree/actions) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=Lodin_react-vtree&metric=coverage)](https://sonarcloud.io/dashboard?id=Lodin_react-vtree) [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=Lodin_react-vtree&metric=bugs)](https://sonarcloud.io/dashboard?id=Lodin_react-vtree) [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=Lodin_react-vtree&metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=Lodin_react-vtree) This package provides a lightweight and flexible solution for rendering large tree structures. It is built on top of the [react-window](https://github.com/bvaughn/react-window) library. **Attention!** This library is entirely rewritten to work with the `react-window`. If you are looking for the tree view solution for the [react-virtualized](https://github.com/bvaughn/react-virtualized), take a look at [react-virtualized-tree](https://github.com/diogofcunha/react-virtualized-tree). ## Installation ```bash # npm npm i react-window react-vtree # Yarn yarn add react-window react-vtree ``` ## Usage ### `FixedSizeTree` #### Example You can also take a look at the very similar example at the Storybook: - [Source code](./__stories__/FixedSizeTree.story.tsx) - [Demo](https://lodin.github.io/react-vtree/index.html?path=/story/tree--fixedsizetree) ```javascript import {FixedSizeTree as Tree} from 'react-vtree'; // Tree component can work with any possible tree structure because it uses an // iterator function that the user provides. Structure, approach, and iterator // function below is just one of many possible variants. const tree = { name: 'Root #1', id: 'root-1', children: [ { children: [ {id: 'child-2', name: 'Child #2'}, {id: 'child-3', name: 'Child #3'}, ], id: 'child-1', name: 'Child #1', }, { children: [{id: 'child-5', name: 'Child #5'}], id: 'child-4', name: 'Child #4', }, ], }; function* treeWalker(refresh) { const stack = []; // Remember all the necessary data of the first node in the stack. stack.push({ nestingLevel: 0, node: tree, }); // Walk through the tree until we have no nodes available. while (stack.length !== 0) { const { node: {children = [], id, name}, nestingLevel, } = stack.pop(); // Here we are sending the information about the node to the Tree component // and receive an information about the openness state from it. The // `refresh` parameter tells us if the full update of the tree is requested; // basing on it we decide to return the full node data or only the node // id to update the nodes order. const isOpened = yield refresh ? { id, isLeaf: children.length === 0, isOpenByDefault: true, name, nestingLevel, } : id; // Basing on the node openness state we are deciding if we need to render // the child nodes (if they exist). if (children.length !== 0 && isOpened) { // Since it is a stack structure, we need to put nodes we want to render // first to the end of the stack. for (let i = children.length - 1; i >= 0; i--) { stack.push({ nestingLevel: nestingLevel + 1, node: children[i], }); } } } } // Node component receives all the data we created in the `treeWalker` + // internal openness state (`isOpen`), function to change internal openness // state (`toggle`) and `style` parameter that should be added to the root div. const Node = ({data: {isLeaf, name}, isOpen, style, toggle}) => ( <div style={style}> {!isLeaf && ( <button type="button" onClick={toggle}> {isOpen ? '-' : '+'} </button> )} <div>{name}</div> </div> ); const Example = () => ( <Tree treeWalker={treeWalker} itemSize={30} height={150} width={300}> {Node} </Tree> ); ``` #### Props The component receives all the props of the `FixedSizeList` component except for the `itemCount`. Additional properties are the following: ##### `children` The `Node` component that is responsible for rendering each node. It receives all the properties [`Row`](https://react-window.now.sh/#/api/FixedSizeList) recieves except for the `index` prop plus the following properties: - `data: object` - a data object yielded by the `treeWalker` function. - `isOpen: boolean` - a current openness status of the node. - `toggle(): function` - a function to change the openness state of the node. It receives no arguments and can be provided directly as an `onClick` handler. - `treeData: any` - any data provided via the `itemData` property of the `FixedSizeTree` component. ##### `rowComponent: component` This property receives a custom `Row` component for the `FixedSizeList` that will override the default one. It can be used for adding new functionality to an existing one by wrapping the default `Row` into a custom component. ##### `* treeWalker(refresh: boolean)` An iterator function that walks around the tree and yields each node one by one flattening them to an array that can be easily displayed by `FixedSizeList` component. The function receives `refresh` parameter. If it is `true`, the component requests the full node update and expects the complete data object yielded. If it is `false`, the component awaits only the node id to update the order of displayed nodes. The data object should contain the following required properties: - `id` - a unique identifier of the node. - `isOpenByDefault` - a default openness state of the node. You can add any other property you need. This object will be sent directly to the `Node` component. Yielding the object gets the current openness state of the node. Basing on it, you should decide if the node's children are going to be rendered. #### Methods The component provides all the methods `FixedSizeList` provides with the following changes: ##### `scrollToItem(id: string | symbol, align?: Align): void` The `scrollToItem` method receives node `id` instead of `index`. ##### `async recomputeTree(options): void` This method runs the `treeWalker` function again and, basing on the received options, updates either nodes or their order. It receives options object with the following parameters: - `opennessState: Record<string, boolean>` - nodes whose IDs are specified as keys of this object will be opened or closed according to boolean values. If the value is `true`, node will be opened; otherwise, it will be closed. This object can be used for changing nodes' openness programmatically without re-creating the `treeWalker` generator. **NOTE**: If you specify both `useDefaultOpenness` and `opennessState`, `opennessState` will be overridden by `useDefaultOpenness` results. - `refreshNodes: boolean` - if this parameter is `true`, `treeWalker` will receive `refresh` option, and the component will expect the data object yielded. If this parameter is either `false` or not provided, the component will expect string id. - `useDefaultOpenness: boolean` - if this parameter is `true`, openness state of all nodes will be reset to `isOpenByDefault`. Nodes updated during the tree walking will use the new `isOpenByDefault` value. #### Types - `FixedSizeNodeData` - object the `treeWalker` generator function yields for each new node. By default, it contains only `id` and `isOpenByDefault` fields, but you can add any number of additional fields; they will be sent directly to the Node component. To describe that data, you have to create a new type that extends the `FixedSizeNodeData` type. - `FixedSizeNodeComponentProps<T extends FixedSizeNodeData>` - props that `Node` component receives. They are described in the Props [children](#children) section. - `FixedSizeTreeProps<T extends FixedSizeNodeData>` - props that `FixedSizeTree` component receives. Described in the [Props](#props) section. - `FixedSizeTreeState<T extends FixedSizeNodeData>` - state that `FixedSizeTree` component has. ### `VariableSizeTree` #### Example You can also take a look at the very similar example at the Storybook: - [Source code](./__stories__/VariableSizeTree.story.tsx) - [Demo](https://lodin.github.io/react-vtree/index.html?path=/story/tree--variablesizetree) ```javascript import {VariableSizeTree as Tree} from 'react-vtree'; // Tree component can work with any possible tree structure because it uses an // iterator function that the user provides. Structure, approach, and iterator // function below is just one of many possible variants. const tree = { name: 'Root #1', id: 'root-1', children: [ { children: [ {id: 'child-2', name: 'Child #2'}, {id: 'child-3', name: 'Child #3'}, ], id: 'child-1', name: 'Child #1', }, { children: [{id: 'child-5', name: 'Child #5'}], id: 'child-4', name: 'Child #4', }, ], }; function* treeWalker(refresh) { const stack = []; stack.push({ nestingLevel: 0, node: tree, }); while (stack.length !== 0) { const { node: {children = [], id, name}, nestingLevel, } = stack.pop(); const isOpened = yield refresh ? { // The only difference VariableSizeTree `treeWalker` has comparing to // the FixedSizeTree is the `defaultHeight` property in the data // object. defaultHeight: 30, id, isLeaf: children.length === 0, isOpenByDefault: true, name, nestingLevel, } : id; if (children.length !== 0 && isOpened) { for (let i = children.length - 1; i >= 0; i--) { stack.push({ nestingLevel: nestingLevel + 1, node: children[i], }); } } } } // Node component receives current node height as a prop const Node = ({data: {isLeaf, name}, height, isOpen, style, toggle}) => ( <div style={style}> {!isLeaf && ( <button type="button" onClick={toggle}> {isOpen ? '-' : '+'} </button> )} <div>{name}</div> </div> ); const Example = () => ( <Tree treeWalker={treeWalker} height={150} width={300}> {Node} </Tree> ); ``` #### Props The component receives all the props of the `VariableSizeList` component except for the `itemCount` and `itemSize`. `itemSize` is still available but not required, and should be used only if the default behavior is not enough. Additional properties are the following: ##### `children` The `Node` component. It is the same as the [`FixedSizeTree`](#fixedsizetree)'s one but receives two additional properties: - `height: number` - a current height of the node. - `resize(newHeight: number, shouldForceUpdate?: boolean): function` - a function to change the height of the node. It receives two parameters: - `newHeight: number` - a new height of the node. - `shouldForceUpdate: boolean` - an optional argument that will be sent to the [`resetAfterIndex`](https://react-window.now.sh/#/api/VariableSizeList) method. ##### `rowComponent: component` This property receives a custom `Row` component for the `VariableSizeList` that will override the default one. It can be used for adding new functionality to an existing one by wrapping the default `Row` into a custom component. ##### `* treeWalker(refresh: boolean)` An iterator function that walks over the tree. It behaves the same as `FixedSizeTree`'s `treeWalker`, but there one additional required property for the data object: - `defaultHeight: number` - the default height of the node. #### Methods The component provides all the methods `VariableSizeList` provides with the following changes: ##### `scrollToItem(id: string | symbol, align?: Align): void` The `scrollToItem` method receives node `id` instead of `index`. ##### `resetAfterId(id: string | symbol, shouldForceUpdate: boolean = false): void` This method replaces the `resetAfterIndex` method of `VariableSizeList`, but works exactly the same. It receives node `id` as a first argument. ##### `async recomputeTree(options): void` This method works exactly the same as the `FixedSizeTree`'s one, but receives one additional option: - `useDefaultHeight: boolean` - if this parameter is `true`, the height of all nodes will be reset to `defaultHeight`. Nodes updated during the tree walking will use the new `defaultHeight` value. #### Types All types in this section are the extended variants of [`FixedSizeTree` types](#types). - `VariableSizeNodeData` - `VariableSizeNodeComponentProps<T extends VariableSizeNodeData>`. - `VariableSizeTreeProps<T extends VariableSizeNodeData>`. - `VariableSizeTreeState<T extends VariableSizeNodeData>`.