|
1 | 1 | # useTree
|
2 | 2 | React components for tree structures
|
3 | 3 |
|
| 4 | +`useTree` is a set of components and hooks that make it ridiculously easy to work with lazy-loaded tree structures like navigation menus or tables of contents. |
| 5 | + |
| 6 | +With `useTree` you can focus on how to render your tree structure and forget about loading data and managing tree state. `useTree` is easy to implement for any data source you can think of. |
| 7 | + |
4 | 8 | - [Installation](#installation)
|
| 9 | +- [Overview](#overview) |
| 10 | +- [Components and hooks](#components-and-hooks) |
| 11 | + - [TreeContainer](#treecontainer) |
| 12 | + - [interface TreeSource](#interface-treesource) |
| 13 | + - [interface TreeState](#interface-treestate) |
| 14 | + - [useTreeController()](#usetreecontroller) |
| 15 | + - [useTreeNodeController(id: string)](#usetreenodecontrollerid-string) |
| 16 | + - [useTreeContent()](#usetreecontent) |
| 17 | + - [useTreeLoader()](#usetreeloader) |
| 18 | +- [Rendering a tree](#rendering-a-tree) |
| 19 | + - [Type Tree<T>](#type-treet) |
| 20 | + - [Type TreeNode<T>](#type-treenodet) |
5 | 21 | - [Typescript](#typescript)
|
6 | 22 | - [Developer](#developer)
|
7 | 23 |
|
8 | 24 | ## Installation
|
9 | 25 | `npm install -S use-tree`
|
10 | 26 |
|
| 27 | +## Overview |
| 28 | +Using `useTree` is as easy as: |
| 29 | + |
| 30 | +```tsx |
| 31 | +import { TreeContainer } from 'use-tree'; |
| 32 | +import { myTreeDataSource } from './data'; |
| 33 | +import { List } from './list'; |
| 34 | + |
| 35 | +const MyTreeComponent = () => ( |
| 36 | + <TreeContainer source={myTreeDataSource} rootElement={List} /> |
| 37 | +); |
| 38 | +``` |
| 39 | + |
| 40 | +Here, `myTreeDataSource` is responsible for loading the data for your tree elements, and `List` is a custom component of your choice that renders the data from that tree. |
| 41 | + |
| 42 | +What `TreeContainer` will do for you is: |
| 43 | + |
| 44 | +* Call `myTreeDataSource` to load all required data. |
| 45 | +* Pass the currently loaded tree data to `List`, and update that data to re-render whenever new data arrives. |
| 46 | +* Manage which tree nodes are expanded and collapsed and use that information to load new data from `myTreeDataSource`. |
| 47 | +* Include information in the data passed to `List` to allow it to show a loading state when child nodes are being fetched from the source. |
| 48 | +* Make sure that re-rendering `List` works with `React.memo()` so that only the nodes that really need updates are re-rendered. |
| 49 | +* Allow components inside `List` to expand and collapse tree nodes in one line of code. |
| 50 | +* Allow you to make one tree node "active" and load and expand all nodes above it. |
| 51 | + |
| 52 | +## Components and hooks |
| 53 | +`useTree` provides these components and hooks: |
| 54 | + |
| 55 | +* `TreeContainer`: the easiest entry point for normal use. Wrap this around your component, pass a data source of interface `TreeSource`, and you're usually good to go. |
| 56 | +* `useTreeLoader()`: take a source and the current tree state and load data from the source to expand the tree, returning a simple an up-to-date data structure with tree data. |
| 57 | +* `useTreeController()` / `useTreeNodeController(id: string)`: return an object that allows you to control the state of the current tree (from context). |
| 58 | +* `useTreeContent()`: return the data for the current tree (from context). |
| 59 | + |
| 60 | +We will now describe these in more detail. |
| 61 | + |
| 62 | +### TreeContainer |
| 63 | +The main wrapper component. Start here. This sets up tree loading and a tree context for a data source. It also allows you to pass the tree state and allow you to manage the tree state yourself. |
| 64 | + |
| 65 | +Properties: |
| 66 | + |
| 67 | +* `source: TreeSource`: the data source that `useTree` will fetch tree data from (described below). |
| 68 | +* `state?: TreeState`: the current state of the tree. Pass this to use `TreeContainer` in controlled mode. This property tells `useTree` which tree nodes are expanded and which (if any) is active. If you pass a new state, `useTree` will update the tree data immediately and load the required data. |
| 69 | +* * `defaultState?: TreeState`: the state to start with. If you pass this, but not `state`, then `TreeContainer` will manage state internally (uncontrolled). |
| 70 | +* `onStateChange?: (st: TreeState) => void`: called whenever the tree state changes from within (usually through `useTreeController()`). Use this if you want to manage tree state in your own state container (like Redux). |
| 71 | + |
| 72 | +### interface TreeSource |
| 73 | +Interface for a data source to fetch tree data, usually from a server. A data source should implement these two methods: |
| 74 | + |
| 75 | +* `children(id: string | null): Promise<Array<TreeSourceNode<T>>>`: fetch an array of all children of a specified node, or all root elements if `id` is `null`. |
| 76 | +* `trail(id: string): Promise<Array<TreeSourceNode<T>>>`: fetch a tree node *and all its ancestors*. The first element of the array should be the node, the seconds its parent, the third its parent's parent, all the way up to the root. |
| 77 | + |
| 78 | +Type `TreeSourceNode<T>` contains all the properties of `T` (which is a type that you can define) as well as these properties: |
| 79 | + |
| 80 | +* `id: string`: a unique identifier (within the tree) of the node. |
| 81 | +* `hasChildren: boolean`: whether or not this node has child elements. |
| 82 | + |
| 83 | +### interface TreeState |
| 84 | +This interface describes the current display state of a tree. It contains two properties: |
| 85 | + |
| 86 | +* `activeId?: string | null`: the ID of the tree node that is *active*. Within a navigation tree, this usually represents the current page or chapter. Setting an ID here causes `useTree` to load all of the ancestors of this node and all their child elements, to allow you to display the active item within its tree. |
| 87 | +* `expandedIds?: { [k: string]: boolean }`: a dictionary of which elements are (or are not) expanded. Adding an element here as expanded will cause `useTree` to load its child elements, if they are not already known. |
| 88 | + |
| 89 | +### useTreeController() |
| 90 | +Get an object that lets you control the current tree (from context). Provides these methods: |
| 91 | + |
| 92 | +* `updateState(updater: (oldState: TreeState) => TreeState): void`: update the tree state with your own updater function. |
| 93 | +* `setExpanded(id: string, expanded?: boolean): void`: expanded or collapse a tree node. |
| 94 | +* `toggleExpanded(id: string): void`: toggle the expanded state of a tree node. |
| 95 | +* `setActiveId(id: string | null): void`: set which (if any) tree node is active. |
| 96 | + |
| 97 | +### useTreeNodeController(id: string) |
| 98 | +Same as `useTreeController()`, but only for one tree node. You should, for instance, use this inside the component you use to render your tree nodes to control their expanded/collapsed state. Provides these methods: |
| 99 | + |
| 100 | +* `setExpanded(expanded?: boolean): void`. |
| 101 | +* `toggleExpanded(): void`. |
| 102 | +* `setActive(active?: boolean): void`. |
| 103 | + |
| 104 | +### useTreeContent() |
| 105 | +When used inside the context of a tree, returns the current data of the tree. This can be used if you have several components nested inside your `<TreeContainer>` that all want to display the tree (or parts thereof). |
| 106 | + |
| 107 | +### useTreeLoader() |
| 108 | +Use this if you want full control and don't want to use `TreeContainer`. This hook takes a `TreeSource` and a `TreeState` and returns the most up-to-date tree data structure. It will load data from the source if necessary and re-render as that data comes in. |
| 109 | + |
| 110 | +## Rendering a tree |
| 111 | +When rendering a tree through the `rootElement` of `TreeContainer` or by passing the result of `useTreeLoader()` directly to your component, your component should accept these data types. We will assume that your `TreeSource` is of type `TreeSource<T>` where `T` is your own type that contains your own properties for tree nodes. |
| 112 | + |
| 113 | +Root element properties: |
| 114 | +* `tree: Tree<T>` |
| 115 | + |
| 116 | +### Type Tree<T> |
| 117 | +* `isLoading: boolean`: whether the items are still being loaded. |
| 118 | +* `items: Array<TreeNode<T>>`: an array of the currently loaded child nodes. |
| 119 | + |
| 120 | +### Type TreeNode<T> |
| 121 | +* All properties from `T` |
| 122 | +* `id: string` |
| 123 | +* `hasChildren: boolean` |
| 124 | +* `isExpanded: boolean` |
| 125 | +* `isActive: boolean` |
| 126 | +* `isActiveTrail: boolean` |
| 127 | +* `children: Tree<T>` |
| 128 | + |
11 | 129 | ## Typescript
|
12 | 130 | `useTree` supports Typescript and contains generic typings. Of course you can also use it in plain old Javascript.
|
13 | 131 |
|
|
0 commit comments