22sidebar_position : 2
33---
44
5- # Custom Data Provider
5+ # Data Provider
66
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.
8156This provider must implement the [TreeDataProvider interface ](/ docs / api / interfaces / TreeDataProvider ), i .e .
9157
10158` ` ` typescript
@@ -27,3 +175,139 @@ tree structure should be handled, i.e. by renaming an item or moving items from
27175another . You still need to enable this functionality in the environment by providing the respective
28176flags . Look into the [TreeCapabilities interface ](/ docs / api / interfaces / TreeCapabilities ) for more details
29177on 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