Skip to content

Commit eba9872

Browse files
committed
Add a readme, simplify the types
1 parent 330858f commit eba9872

File tree

5 files changed

+126
-7
lines changed

5 files changed

+126
-7
lines changed

README.md

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,131 @@
11
# useTree
22
React components for tree structures
33

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+
48
- [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)
521
- [Typescript](#typescript)
622
- [Developer](#developer)
723

824
## Installation
925
`npm install -S use-tree`
1026

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+
11129
## Typescript
12130
`useTree` supports Typescript and contains generic typings. Of course you can also use it in plain old Javascript.
13131

src/Tree.stories.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ async function timeout(ms) {
2121
}
2222

2323
const testSource = {
24-
async children(id?: string | null | undefined) {
24+
async children(id: string | null) {
2525
console.log('source load children', id);
2626
const parentId = id || '';
2727
await timeout(100);
@@ -97,7 +97,7 @@ const TreeExampleContainer: React.FC<{ activeId?: string }> = ({ activeId }) =>
9797
}
9898

9999
return (
100-
<TreeContainer source={testSource} state={state} setState={setState} rootElement={List} />
100+
<TreeContainer source={testSource} state={state} onStateChange={setState} rootElement={List} />
101101
);
102102
};
103103

src/TreeContainer.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ interface TreeContainerProps<T> {
99
source: TreeSource<T>;
1010
defaultState?: TreeState;
1111
state?: TreeState;
12-
setState?: (st: TreeState) => void;
12+
onStateChange?: (st: TreeState) => void;
1313
rootElement?: React.FC<{ tree: Tree<T> }>;
1414
}
1515

1616
export function TreeContainer<T>(props: PropsWithChildren<TreeContainerProps<T>>, context?: any): ReactElement | null {
17-
const { source, defaultState, state, setState, rootElement, children } = props;
17+
const { source, defaultState, state, onStateChange, rootElement, children } = props;
1818
const controller = useRef<TreeController>(treeControllerFromUpdateState(noopUpdateState));
19-
const [innerState, setInnerState] = useBinding(defaultState, state, setState, {});
19+
const [innerState, setInnerState] = useBinding(defaultState, state, onStateChange, {});
2020

2121
const tree = useTreeLoader(source, innerState);
2222

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export {
22
Tree,
33
TreeNode,
4+
TreeSource,
45
TreeState,
56
} from './types';
67
export {

src/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export interface LoadableArray<T> {
99
}
1010

1111
export interface TreeSource<T> {
12-
children(id?: string | null | undefined): Promise<Array<TreeSourceNode<T>>>;
12+
children(id: string | null): Promise<Array<TreeSourceNode<T>>>;
1313
trail(id: string): Promise<Array<TreeSourceNode<T>>>;
1414
}
1515

@@ -22,7 +22,7 @@ export type TreeNode<T> = TreeSourceNode<T> & {
2222
isExpanded: boolean;
2323
isActive: boolean;
2424
isActiveTrail: boolean;
25-
children: LoadableArray<TreeNode<T>>;
25+
children: Tree<T>;
2626
};
2727

2828
export type Tree<T> = LoadableArray<TreeNode<T>>;

0 commit comments

Comments
 (0)