2
2
sidebar_position : 2
3
3
---
4
4
5
- # Custom Data Provider
5
+ # Data Provider
6
6
7
- When using an uncontrolled environment, you need to provide your data by supplying a data provider.
7
+ When using an uncontrolled environment, you need to provide your data by supplying a data provider. The
8
+ easiest way to get started is using the [ Static Tree Data Provider] ( /docs/api/classes/StaticTreeDataProvider ) .
9
+ It allows you to provide your data as record which maps item ids to tree items, and gives you the possibility
10
+ to react to changes in the tree structure, as well as inject your own changes through change events.
11
+
12
+ # Static Tree Data Provider
13
+
14
+ The following example gives a good example of what is possible with static tree data providers. We will look
15
+ into the details of the data provider below.
16
+
17
+ ``` jsx live
18
+ function App () {
19
+ const items = useMemo (() => ({ ... shortTree .items }), []);
20
+ const dataProvider = useMemo (
21
+ () =>
22
+ new StaticTreeDataProvider (items, (item , data ) => ({
23
+ ... item,
24
+ data,
25
+ })),
26
+ [items]
27
+ );
28
+
29
+ const injectItem = () => {
30
+ const rand = ` ${ Math .random ()} ` ;
31
+ items[rand] = { data: ' New Item' , index: rand };
32
+ items .root .children .push (rand);
33
+ dataProvider .onDidChangeTreeDataEmitter .emit ([' root' ]);
34
+ };
35
+
36
+ const removeItem = () => {
37
+ if (items .root .children .length === 0 ) return ;
38
+ items .root .children .pop ();
39
+ dataProvider .onDidChangeTreeDataEmitter .emit ([' root' ]);
40
+ };
41
+
42
+ return (
43
+ < UncontrolledTreeEnvironment
44
+ canDragAndDrop
45
+ canDropOnFolder
46
+ canReorderItems
47
+ dataProvider= {dataProvider}
48
+ getItemTitle= {item => item .data }
49
+ viewState= {{
50
+ ' tree-1' : {
51
+ expandedItems: [],
52
+ },
53
+ }}
54
+ >
55
+ < button type= " button" onClick= {injectItem}>
56
+ Inject item
57
+ < / button>
58
+ < button type= " button" onClick= {removeItem}>
59
+ Remove item
60
+ < / button>
61
+ < Tree treeId= " tree-1" rootItem= " root" treeLabel= " Tree Example" / >
62
+ < / UncontrolledTreeEnvironment>
63
+ );
64
+ }
65
+ ```
66
+
67
+ ## Creating the data provider with data
68
+
69
+ First, create the data provider. You want to make sure it isn't recreated on re-renders, so memoize
70
+ it in the component in which it is defined.
71
+
72
+ ``` tsx
73
+ const dataProvider = useMemo (
74
+ () =>
75
+ new StaticTreeDataProvider (items , (item , data ) => ({
76
+ ... item ,
77
+ data ,
78
+ })),
79
+ [items ]
80
+ );
81
+ ```
82
+
83
+ The items is a record mapping item ids to tree items, for example:
84
+
85
+ ``` typescript
86
+ const items = [
87
+ {
88
+ index: " item-id" ,
89
+ data: { arbitraryData: 123 , name: " Hello" },
90
+ children: [" item-id-1" , " item-id-2" ],
91
+ isFolder: true
92
+ }
93
+ ]
94
+ ```
95
+
96
+ Note that, whatever you provide to the ` getItemTitle ` prop is used to infer the item display name.
97
+
98
+ ``` ts jsx
99
+ < UncontrolledTreeEnvironment
100
+ getItemTitle = {item => item .data .name }
101
+ / >
102
+ ```
103
+
104
+ ## Apply changes from outside
105
+
106
+ You can apply changes to the underlying data source. Just make sure to let RCT know about that by
107
+ emitting a change event on the affected items. Note that, if you add or remove items, the affected item
108
+ is the parent item, not the added or removed items.
109
+
110
+ ``` ts
111
+ const injectItem = () => {
112
+ const rand = ` ${Math .random ()} ` ;
113
+ items [rand ] = { data: ' New Item' , index: rand };
114
+ items .root .children .push (rand );
115
+ dataProvider .onDidChangeTreeDataEmitter .emit ([' root' ]);
116
+ };
117
+
118
+ const removeItem = () => {
119
+ if (items .root .children .length === 0 ) return ;
120
+ items .root .children .pop ();
121
+ dataProvider .onDidChangeTreeDataEmitter .emit ([' root' ]);
122
+ };
123
+ ```
124
+
125
+ ## Reacting to Drag Events
126
+
127
+ Drag changes are always immediately applied to the visualization, so make sure to implement the ` canDropAt `
128
+ prop to customize if that should not work in all cases. The static tree data emits tree change events similar
129
+ to the ones you would emit when applying changes from outside, so you can react to them in the same way.
130
+
131
+ ``` typescript
132
+ dataProvider .onDidChangeTreeData (changedItemIds => {
133
+ console .log (changedItemIds );
134
+ });
135
+ ```
136
+
137
+ ## Reacting to Rename Events
138
+
139
+ The second (optional) parameter of the static tree data provider lets you react to rename events. Note that
140
+ you can customize whether renaming is possible in the first place through the ` canRename ` prop.
141
+
142
+ ``` typescript
143
+ const dataProvider = new StaticTreeDataProvider (items , (item , newName ) => {
144
+ // Return the patched item with new item name here
145
+ return {
146
+ ... item ,
147
+ data: { ... item .data , name: newName },
148
+ };
149
+ });
150
+ `
151
+ ` ` `
152
+
153
+ ## Custom Data Provider
154
+
155
+ In more complex scenarios , it ' s probably easiest to implement your own data provider.
8
156
This provider must implement the [TreeDataProvider interface ](/ docs / api / interfaces / TreeDataProvider ), i .e .
9
157
10
158
` ` ` typescript
@@ -27,3 +175,139 @@ tree structure should be handled, i.e. by renaming an item or moving items from
27
175
another . You still need to enable this functionality in the environment by providing the respective
28
176
flags . Look into the [TreeCapabilities interface ](/ docs / api / interfaces / TreeCapabilities ) for more details
29
177
on the necessary flags .
178
+
179
+ You can use this implementation as baseline :
180
+
181
+ ` ` ` typescript
182
+
183
+ class CustomDataProviderImplementation implements TreeDataProvider {
184
+ private data: Record<TreeItemIndex, TreeItem> = { ...shortTree.items };
185
+
186
+ private treeChangeListeners: ((changedItemIds: TreeItemIndex[]) => void)[] =
187
+ [];
188
+
189
+ public async getTreeItem(itemId: TreeItemIndex) {
190
+ return this.data[itemId];
191
+ }
192
+
193
+ public async onChangeItemChildren(
194
+ itemId: TreeItemIndex,
195
+ newChildren: TreeItemIndex[]
196
+ ) {
197
+ this.data[itemId].children = newChildren;
198
+ this.treeChangeListeners.forEach(listener => listener([itemId]));
199
+ }
200
+
201
+ public onDidChangeTreeData(
202
+ listener: (changedItemIds: TreeItemIndex[]) => void
203
+ ): Disposable {
204
+ this.treeChangeListeners.push(listener);
205
+ return {
206
+ dispose: () =>
207
+ this.treeChangeListeners.splice(
208
+ this.treeChangeListeners.indexOf(listener),
209
+ 1
210
+ ),
211
+ };
212
+ }
213
+
214
+ public async onRenameItem(item: TreeItem<any>, name: string): Promise<void> {
215
+ this.data[item.index].data = name;
216
+ }
217
+
218
+ // custom handler for directly manipulating the tree data
219
+ public injectItem(name: string) {
220
+ const rand = ` $ {Math .random ()}` ;
221
+ this.data[rand] = { data: name, index: rand } as TreeItem;
222
+ this.data.root.children?.push(rand);
223
+ this.treeChangeListeners.forEach(listener => listener(['root']));
224
+ }
225
+ }
226
+ ` ` `
227
+
228
+ ## Reacting to Drag Events
229
+
230
+ RCT will call ` onChangeItemChildren ` when a drag operation is finished . You can use this directly
231
+ to update your data source . Note that , if you add or remove items , the affected item
232
+ is the parent item , not the added or removed items .
233
+
234
+ In the exemplary implementation above , this emits an event on the ` treeChangeListeners ` listeners ,
235
+ where you could register a custom listener to react to changes .
236
+
237
+ ## Reacting to Rename Events
238
+
239
+ RCT will call ` onRenameItem ` when a rename operation is finished . Implement your rename logic there .
240
+
241
+ ## Custom Provider Live Demo
242
+
243
+ ` ` ` jsx live
244
+ function App() {
245
+ const dataProvider = useMemo(
246
+ () => {
247
+ class CustomDataProviderImplementation {
248
+ data = { ...shortTree.items };
249
+
250
+ treeChangeListeners = [];
251
+
252
+ async getTreeItem(itemId) {
253
+ return this.data[itemId];
254
+ }
255
+
256
+ async onChangeItemChildren(itemId, newChildren) {
257
+ this.data[itemId].children = newChildren;
258
+ this.treeChangeListeners.forEach(listener => listener([itemId]));
259
+ }
260
+
261
+ onDidChangeTreeData(listener) {
262
+ this.treeChangeListeners.push(listener);
263
+ return {
264
+ dispose: () =>
265
+ this.treeChangeListeners.splice(
266
+ this.treeChangeListeners.indexOf(listener),
267
+ 1
268
+ ),
269
+ };
270
+ }
271
+
272
+ async onRenameItem(item, name) {
273
+ this.data[item.index].data = name;
274
+ }
275
+
276
+ injectItem(name) {
277
+ const rand = ` $ {Math .random ()}` ;
278
+ this.data[rand] = { data: name, index: rand };
279
+ this.data.root.children.push(rand);
280
+ this.treeChangeListeners.forEach(listener => listener(['root']));
281
+ }
282
+ }
283
+ return new CustomDataProviderImplementation()
284
+ },
285
+ []
286
+ );
287
+
288
+ return (
289
+ <UncontrolledTreeEnvironment
290
+ canDragAndDrop
291
+ canDropOnFolder
292
+ canReorderItems
293
+ dataProvider={dataProvider}
294
+ getItemTitle={item => item.data}
295
+ viewState={{
296
+ 'tree-2': {
297
+ expandedItems: [],
298
+ },
299
+ }}
300
+ >
301
+ <button
302
+ type="button"
303
+ onClick={() =>
304
+ dataProvider.injectItem(window.prompt('Item name') || 'New item')
305
+ }
306
+ >
307
+ Inject item
308
+ </button>
309
+ <Tree treeId="tree-2" rootItem="root" treeLabel="Tree Example" />
310
+ </UncontrolledTreeEnvironment>
311
+ );
312
+ }
313
+ ` ` `
0 commit comments