Handling external state updates
Generally, Headless Tree loads the tree structure once when it is loaded, and loads subsequent substructures
whenever parts of the visual tree change, e.g. because a folder is expanded that was collapsed before.
Headless Tree provides ways to mutate the tree via drag events or renaming, and makes sure that it reloads
those parts that have changed automatically if one of
removeItemsFromParents()
, insertItemsAtTarget()
or createOnDropHandler()
was used (see the Guide on mutating your data after the Drop for details).
However, in many cases you will want to update the tree structure of your data based on external events. In this case you need to inform Headless Tree, that the tree structure has changed, so that it can update the rendering of the affected parts.
Synchronous Trees
For Trees using the syncDataLoader
feature, the gist of data loading is as follows:
Headless Tree maintains an internal representation of a flattened view of the tree, that is generated based on
the dataLoader
information that is available when the tree renders. This information is regenerated whenever
tree.rebuildTree()
is called, which is done automatically when items are expanded/collapsed
or drag events are fulfilled with one of the aforementioned drag-related update-methods. The react component then
needs to re-render for the visual changes to come into affect, which is also done automatically in those cases since
tree.rebuildTree()
triggers an update to the tree state which will also re-render the component.
If you mutate the data, you will need to call tree.rebuildTree()
manually, so that Headless Tree can update the internal
representation of the tree. Make sure that this call happens after the data loader has access to the mutated data.
const items = {
item1: { name: "Item 1", children: ["item2"] },
item2: { name: "Item 2", children: [] },
}
const dataLoader = {
getItem: (id: string) => items[id],
getChildren: (id: string) => items[id].children,
};
const addNode = () => {
items["item1"].children.push("item3");
items["item3"] = { name: "Item 3", children: [] };
tree.rebuildTree();
};
const removeNode = () => {
delete items["item2"];
items["item1"].children = items["item1"].children.filter(id => id !== "item2");
tree.rebuildTree();
};
The following sample shows how to add and delete items from a synchronous tree, as well mutating their data by renaming them.
Asynchronous Trees
For asynchronous trees, the general gist of how trees are updated is the same, however the asyncDataLoaderFeature
provides the additional functionality of caching item information of tree data, which means that just triggering
tree.rebuildTree()
will do nothing as it will just keep using the stale information before the update.
When using the asyncDataLoaderFeature
, additional methods are available on item instances to trigger them
to invalidate their data:
const item = tree.getItemInstance("item1");
item.invalidateItemData();
item.invalidateChildrenIds();
You will not have to additionally call tree.rebuildTree()
, as this is done automatically by the async
data loader feature once it has refetched the data.
Instead of invalidating and waiting for a refetch, you can also directly update the cached data of an item or its cached children ids, if you already have the new data available:
const item = tree.getItemInstance("item1");
item.updateCachedData({ ...item.getItemData(), name: "New name" });
item.updateCachedChildrenIds(["child1", "child2"]);
In the demo below, you can see the same samples for adding and deleting data as above, but with
an async data loader instead. In this sample, the cache is updated with item.updateCachedChildrenIds()
and item.updateCachedData()
. Note that you do need to call tree.rebuildTree()
after mutating
the data this way.
Managing tree data in a React state
Most samples in the documentation use a data loader implementation that lives outside of React, since in most cases the data loader will directly interface with your backend or other external data source to provide tree data to Headless Tree.
The sample below demonstrates how to manage the tree data in a React state. The most relevant gotcha is that calling
rebuildTree()
may behave unexpectedly in cases where data is stored in a React state. If you mutate your tree data
by updating the React state, the useState
getter will only reflect the new state during the next rerender.
Calling rebuildTree()
immediately after you made your changes, will result in the rebuild logic running on the old
data from before the rerender. To combat this, you can use tree.scheduleRebuildTree()
instead, trigger a rebuild
the next time tree.getItems()
is called, which will be after the next rerender.