ra-tree
Tree hooks and components for react-admin. Allows to display, edit, and rearrange tree structures like directories, categories, etc.
This module is agnostic as to how you store the tree structure in the backend side. Whether you use an array of children, a parent_id
field, materialized paths or nested sets, this module will work. You'll just have to map the data structure expected by react-admin by the one returned by your API in your dataProvider
.
Test it live in the Enterprise Edition Storybook and in the e-commerce demo.
Installation
npm install --save @react-admin/ra-tree
# or
yarn add @react-admin/ra-tree
Tip: ra-tree
is part of the React-Admin Enterprise Edition, and hosted in a private npm registry. You need to subscribe to one of the Enterprise Edition plans to access this package.
Usage
dataProvider
The dataProvider
used by the <Admin>
must support tree-specific methods:
Read methods
getTree(resource, { meta })
getRootNodes(resource, { meta })
getParentNode(resource, { childId, meta })
getChildNodes(resource, { parentId, meta })
These methods should return Promises for TreeRecord
objects. A TreeRecord
contains at least an id
field and a children
field (an array of child ids). For instance:
Write methods
moveAsNthChildOf(resource, { source, destination, position, meta })
:source
anddestination
areTreeRecord
objects, andposition
a zero-based integermoveAsNthSiblingOf(resource, { source, destination, position, meta })
:source
anddestination
areTreeRecord
objects, andposition
a zero-based integeraddRootNode(resource, { data, meta })
addChildNode(resource, { parentId, data, meta })
deleteBranch(resource, { id, data, meta })
:id
is the identifier of the node to remove, anddata
its content
These methods should also return Promises for TreeRecord
objects, except for the two moveAs...
methods, which can return an empty data
object if the move is successful.
Tip: All ra-tree
dataProvider methods accept a meta
parameter. it’s a good way to pass custom arguments or metadata to an API call.
Tip: moveAsNthChildOf
is called when you drag and drop a node onto another node. The dragged node will then be inserted as the first child of the destination node. moveAsNthSiblingOf
, on the other hand, is called when you drag and drop a node inbetween other existing nodes (not necessarily from the same branch). The dragged node will then be inserted right below the destination node.
Tip: With moveAsNthSiblingOf
, the position
parameter is a zero-based integer representing the target position in the destination branch, before the move has been performed. This means that the final position may not be equal to the position
parameter, in case you are moving a node on the same branch with the source position being above the destination position. For instance, moving the first node of a branch under the second node will call moveAsNthSiblingOf
with position=2
, however its final position after the move has been performed will be position=1
(remember it's zero-based).
Here is an example of the expected syntax for the additional dataProvider
methods:
dataProvider.getTree('posts').then(({ data }) => console.log(data));
// [
// { id: 1, title: 'foo1', children: [3, 4] },
// { id: 2, title: 'foo2', children: [] },
// { id: 3, title: 'foo3', children: [5] },
// { id: 4, title: 'foo4', children: [] },
// { id: 5, title: 'foo5', children: [] },
// ]
dataProvider.getRootNodes('posts').then(({ data }) => console.log(data));
// [
// { id: 1, title: 'foo1', children: [3, 4] },
// { id: 2, title: 'foo2', children: [] },
// ]
dataProvider
.getParentNode('posts', { childId: 5 })
.then(({ data }) => console.log(data));
// { id: 3, title: 'foo3', children: [5] }
dataProvider
.getChildNodes('posts', { parentId: 1 })
.then(({ data }) => console.log(data));
// [
// { id: 3, title: 'foo3', children: [5] },
// { id: 4, title: 'foo4', children: [] },
// ]
dataProvider
.moveAsNthChildOf('posts', {
source: { id: 5, title: 'foo5', children: [] },
destination: { id: 1, title: 'foo1', children: [3, 4] },
position: 2,
})
.then(({ data }) => console.log(data));
// {}
dataProvider
.moveAsNthSiblingOf('posts', {
source: { id: 5, title: 'foo5', children: [] },
destination: { id: 4, title: 'foo4', children: [] },
position: 1,
})
.then(({ data }) => console.log(data));
// {}
dataProvider
.addRootNode('posts', { data: { title: 'hello' } })
.then(({ data }) => console.log(data));
// { id: 6, titl: 'hello', children: [] }
dataProvider
.addChildNode('posts', { parentId: 2, data: { title: 'hello' } })
.then(({ data }) => console.log(data));
// { id: 6, titl: 'hello', children: [] }
dataProvider
.deleteBranch('posts', {
id: 1,
data: { id: 1, title: 'foo1', children: [3, 4] },
})
.then(({ data }) => console.log(data));
// { id: 1, title: 'foo1', children: [3, 4] }
ra-tree
expects the dataProvider
to return tree structures based on a children
field, but chances are your API stores the tree in another data structure. In that case, you'll need to map the API data structure to the ra-tree data structure in your dataProvider
.
dataProvider
Builders
ra-tree
provides helper functions to create a ra-tree
-compatible dataProvider
based on a regular react-admin dataProvider
- provided the API returns the tree in any of the supported data structures. These helpers add the tree-specific methods described above (getTree()
, getRootNodes()
, etc.) by calling the regular methods (getList()
, getOne()
, etc.).
Be aware that these builders will call the regular dataProvider
several times for each tree method call. We don't recommend using them in production - instead, you should modify your API to support the tree methods, and return data structures in the format expected by ra-tree.
addTreeMethodsBasedOnChildren
If the records returned by your API contain an array of children identifiers, use the addTreeMethodsBasedOnChildren
builder.
Your API should return records with this format:
{
id: 1234,
name: 'hello',
isRoot: false,
children: [45, 356, 1],
}
Example:
import simpleRestProvider from 'ra-data-simple-rest';
import { addTreeMethodsBasedOnChildren } from '@react-admin/ra-tree';
const dataProvider = simpleRestProvider('http://path.to.my.api/');
const dataProviderWithTree = addTreeMethodsBasedOnChildren(
dataProvider,
'children',
'isRoot',
false
);
The builder accepts the following arguments:
dataProvider
: The dataProvider to augment.childrenField
: The name of the field containing the children identifiers. Defaults tochildren
.isRootField
: The name of the field containing the root status. Defaults toisRoot
apiSupportBranchDeletion
: Indicates whether the API will handle children deletion when deleting a node as well as the parent update. If false, the dataProvider will handle it by making multiple requests in the right order. Defaults tofalse
.
addTreeMethodsBasedOnParentAndPosition
If the records returned by your API contain a parent identifier and a position field, use the addTreeMethodsBasedOnParentAndPosition
builder.
Your API should return records with this format:
{
id: 1234,
name: 'hello',
parent_id: 35,
position: 4, // zero-based
}
Example:
import simpleRestProvider from 'ra-data-simple-rest';
import { addTreeMethodsBasedOnParentAndPosition } from '@react-admin/ra-tree';
const dataProvider = simpleRestProvider('http://path.to.my.api/');
const dataProviderWithTree = addTreeMethodsBasedOnParentAndPosition(
dataProvider,
'parent_id',
'position',
false
);
The builder accepts the following arguments:
dataProvider
: The dataProvider to augment.parentIdField
: The name of the field containing the parent identifier. Defaults to 'parent_id'positionField
: The name of the field containing the position of a node inside its parent. Defaults to 'position'apiSupportBranchDeletion
: Indicates whether the API will handle children deletion when deleting a node as well as the siblings update. If false, the dataProvider will handle it by making multiple requests in the right order. Defaults tofalse
.
<Admin>
Setup
This module comes with additional messages, as well as their translations in English and French. To use these messages, add them to your i18nProvider.
This means a typical ra-tree application begins like this:
// in src/App.js
import React from 'react';
import { Admin, Resource, mergeTranslations } from 'react-admin';
import polyglotI18nProvider from 'ra-i18n-polyglot';
import englishMessages from 'ra-language-english';
import { raTreeLanguageEnglish } from '@react-admin/ra-tree';
import dataProvider from './dataProvider';
const i18nProvider = polyglotI18nProvider(locale => {
// Always fallback on english
return mergeTranslations(englishMessages, raTreeLanguageEnglish);
}, 'en');
const App = () => (
<Admin dataProvider={dataProvider} i18nProvider={i18nProvider} locale="en">
...
</Admin>
);
<TreeWithDetails>
Component
Once the dataProvider
and <Admin>
component are ready, you can start using the components of this package. The main one is a replacement for the <List>
component, called <TreeWithDetails>
.
// in src/category.js
import React from 'react';
import {
Admin,
Resource,
Create,
Edit,
SimpleForm,
TextInput,
} from 'react-admin';
import {
CreateNode,
EditNode,
EditNodeToolbar,
TreeWithDetails,
} from '@react-admin/ra-tree';
// a Create view for a tree uses <CreateNode> instead of the standard <Create>
const CategoriesCreate = () => (
<CreateNode>
<SimpleForm>
<TextInput source="name" />
</SimpleForm>
</CreateNode>
);
// an Edit view for a tree uses <EditNode> instead of the standard <Edit>
const CategoriesEdit = () => (
<EditNode>
<SimpleForm toolbar={<EditNodeToolbar />}>
<TextInput source="title" />
</SimpleForm>
</EditNode>
);
// a List view for a tree uses <TreeWithDetails>
export const CategoriesList = () => (
<TreeWithDetails create={CategoriesCreate} edit={CategoriesEdit} />
);
// in src/App.js
import { CategoriesList } from './category';
const App = () => (
<Admin dataProvider={dataProvider}>
<Resource list={CategoriesList} />
</Admin>
);
IMPORTANT: Note that in the Edition view, the
<SimpleForm>
must use the<EditNodeToolbar>
. This toolbar replaces react-admin's default<DeleteButton>
with a ra-tree version that deletes a branch instead of a record.
This also means that if you need to customize the Toolbar
and includes a Delete Button, you must import the aternative button from @react-admin/ra-tree
:
import { Toolbar, ToolbarProps } from 'react-admin';
import { DeleteBranchButton } from '@react-admin/ra-tree';
import MyCustomButton from './MyCustomButton';
export const MyToolbar = (props: ToolbarProps) => (
<Toolbar>
<MyCustomButton />
<DeleteBranchButton />
</Toolbar>
);
import { Toolbar } from "react-admin";
import { DeleteBranchButton } from "@react-admin/ra-tree";
import MyCustomButton from "./MyCustomButton";
export const MyToolbar = (props) => (
<Toolbar>
<MyCustomButton />
<DeleteBranchButton />
</Toolbar>
);
CreateNode
and EditNode
components accept a mutationOptions
prop. So you can override the mutationOptions of the main mutation query.
const CategoriesCreate = () => (
<CreateNode
mutationOptions={{
onSuccess: () => {
console.log('Success!');
},
onError: () => {
console.log('Error');
},
meta: { foo: 'bar' }, // The 'meta' object will be passed to the dataProvider methods
}}
>
<SimpleForm>
<TextInput source="name" />
</SimpleForm>
</CreateNode>
);
react-admin
will fetch the entire tree on mount, and the TreeWithDetails
component will show an interactive tree based on this data. By default, the <TreeWithDetails>
component will use the title
field of the records to display in the tree. You can use an alternative field by setting the titleField
prop in the <TreeWithDetails>
component:
const CategoriesList = (props: ListProps) => (
<TreeWithDetails titleField="name" edit={CategoriesEdit} {...props} />
);
const CategoriesList = (props) => <TreeWithDetails titleField="name" edit={CategoriesEdit} {...props} />;
By default, this package allows only one root per tree. You can allow trees with multiple roots by setting the allowMultipleRoots
prop:
export const CategoriesList = (props: ListProps) => (
<TreeWithDetails
create={CategoriesCreate}
edit={CategoriesEdit}
allowMultipleRoots
{...props}
/>
);
export const CategoriesList = (props) => (
<TreeWithDetails create={CategoriesCreate} edit={CategoriesEdit} allowMultipleRoots {...props} />
);
addRootButton
When allowMultipleRoots
is set to true
or there are no root nodes in the tree, a button is displayed to allow the user to add root nodes. You can pass your own button component using addRootButton
prop:
// in src/posts.js
import { CreateButton } from 'react-admin';
export const CategoriesList = () => (
<TreeWithDetails allowMultipleRoots addRootButton={<CreateButton label="Add Categories!" />}>
...
</TreeWithDetails>
);
// in src/posts.js
import { CreateButton } from "react-admin";
export const CategoriesList = () => (
<TreeWithDetails allowMultipleRoots addRootButton={<CreateButton label="Add Categories!" />}>
...
</TreeWithDetails>
);
Tip: You can hide the add root button completely by passing false
to addRootButton
prop
Title
The default title for a tree view is “[resource] list” (e.g. “Posts list”). Use the title prop to customize the Tree view title:
// in src/posts.js
export const CategoriesList = () => (
<TreeWithDetails title="List of categories">...</TreeWithDetails>
);
// in src/posts.js
export const CategoriesList = () => <TreeWithDetails title="List of categories">...</TreeWithDetails>;
The title can be either a string or an element of your own.
Node Actions
By default, every node has an action dropdown menu displayed after its name when hovered.
While this menu only has a delete action by default, it's possible to customize it.
import {
NodeActions,
DeleteMenuItem,
TreeWithDetails,
} from '@react-admin/ra-tree';
const MyCustomActionMenuItem = forwardRef(
({ record, resource, parentId }, ref) => {
const handleClick = () => {
// Do something with dataProvider ?
};
return (
<MenuItem ref={ref} onClick={handleClick}>
Do something
</MenuItem>
);
}
);
const MyActions = (props: NodeActionsProps) => (
<NodeActions {...props}>
<MyCustomActionMenuItem />
<DeleteMenuItem />
</NodeActions>
);
const CategoriesList = () => (
<TreeWithDetails
titleField="name"
edit={CategoriesEdit}
draggable
showLine
nodeActions={<MyActions />}
/>
);
import { NodeActions, DeleteMenuItem, TreeWithDetails } from "@react-admin/ra-tree";
const MyCustomActionMenuItem = forwardRef(({ record, resource, parentId }, ref) => {
const handleClick = () => {
// Do something with dataProvider ?
};
return (
<MenuItem ref={ref} onClick={handleClick}>
Do something
</MenuItem>
);
});
const MyActions = (props) => (
<NodeActions {...props}>
<MyCustomActionMenuItem />
<DeleteMenuItem />
</NodeActions>
);
const CategoriesList = () => (
<TreeWithDetails titleField="name" edit={CategoriesEdit} draggable showLine nodeActions={<MyActions />} />
);
The menu item will receive the current record and the resource.
Drag and Drop
If you want to allow user to reorder nodes in the tree, simply add the draggable
prop to the <TreeWithDetails>
component:
export const CategoriesList = () => <TreeWithDetails draggable />;
export const CategoriesList = () => <TreeWithDetails draggable />;
Hiding Root Nodes
Sometimes, a tree only has one root node for technical reasons and users should probably not see it at all. Use the hideRootNodes
prop to hide all root nodes.
export const CategoriesList = () => <TreeWithDetails hideRootNodes />;
export const CategoriesList = () => <TreeWithDetails hideRootNodes />;
Lazy Load
If you have a tree with a lot of nodes, you may want to only load the root nodes at first and their children when they are expanded. To enable this mode, set the lazy
prop to true
.
<Tree>
Component
The <Tree>
component is a wrapper for rc-tree's <Tree>
, with Material Design style. It expects a treeData
prop containing a tree of nodes with key
, title
, and children
fields.
// example usage
import { Tree } from '@react-admin/ra-tree';
import treeData from './treeData';
export const SimpleTree = () => <Tree treeData={treeData} />;
// treeData format
[
{
key: '1',
title: 'foo1',
children: [
{
key: '3',
title: 'foo3',
children: [{ key: '5', title: 'foo5', children: [] }],
},
{ key: '4', title: 'foo4', children: [] },
],
},
{ key: '2', title: 'foo2', children: [] },
];
// example usage
import { Tree } from "@react-admin/ra-tree";
import treeData from "./treeData";
export const SimpleTree = () => <Tree treeData={treeData} />;
// treeData format
[
{
key: "1",
title: "foo1",
children: [
{
key: "3",
title: "foo3",
children: [{ key: "5", title: "foo5", children: [] }],
},
{ key: "4", title: "foo4", children: [] },
],
},
{ key: "2", title: "foo2", children: [] },
];
Customizing Translation Messages
This module uses specific translations for displaying buttons and other texts. As for all translations in react-admin, it's possible to customize the messages.
To create your own translations, you can use the TypeScript types to see the structure and see which keys are overridable.
Here is an example of how to customize translations in your app:
import polyglotI18nProvider from 'ra-i18n-polyglot';
import englishMessages from 'ra-language-english';
import frenchMessages from 'ra-language-french';
import {
TranslationMessages as BaseTranslationMessages,
raTreeEnglishMessages,
raTreeFrenchMessages,
RaTreeTranslationMessages,
} from '@react-admin/ra-tree';
/* TranslationMessages extends the defaut translation
* Type from react-admin (BaseTranslationMessages)
* and the ra-tree translation Type (RaTreeTranslationMessages)
*/
interface TranslationMessages
extends RaTreeTranslationMessages,
BaseTranslationMessages {}
const customEnglishMessages: TranslationMessages = mergeTranslations(
englishMessages,
raTreeEnglishMessages,
{
'ra-tree': {
action: {
add_child: 'Add a daughter',
add_root: 'Add a god',
},
},
}
);
const i18nCustomProvider = polyglotI18nProvider(locale => {
if (locale === 'fr') {
return mergeTranslations(frenchMessages, raTreeFrenchMessages);
}
return customEnglishMessages;
}, 'en');
export const MyApp = () => (
<Admin
dataProvider={myDataprovider}
layout={myLayout}
i18nProvider={i18nCustomProvider}
>
...
</Admin>
);
import polyglotI18nProvider from "ra-i18n-polyglot";
import englishMessages from "ra-language-english";
import frenchMessages from "ra-language-french";
import { raTreeEnglishMessages, raTreeFrenchMessages } from "@react-admin/ra-tree";
const customEnglishMessages = mergeTranslations(englishMessages, raTreeEnglishMessages, {
"ra-tree": {
action: {
add_child: "Add a daughter",
add_root: "Add a god",
},
},
});
const i18nCustomProvider = polyglotI18nProvider((locale) => {
if (locale === "fr") {
return mergeTranslations(frenchMessages, raTreeFrenchMessages);
}
return customEnglishMessages;
}, "en");
export const MyApp = () => (
<Admin dataProvider={myDataprovider} layout={myLayout} i18nProvider={i18nCustomProvider}>
...
</Admin>
);
API
<Tree>
Props
Property | Description | Type | Default | Version |
---|---|---|---|---|
autoExpandParent | Whether to automatically expand a parent treeNode | boolean | true | |
blockNode | Whether treeNode fill remaining horizontal space | boolean | false | |
checkable | Adds a Checkbox before the treeNodes |
boolean | false | |
checkedKeys | (Controlled) Specifies the keys of the checked treeNodes (PS: When this specifies the key of a treeNode which is also a parent treeNode, all the children treeNodes of will be checked; and vice versa, when it specifies the key of a treeNode which is a child treeNode, its parent treeNode will also be checked. When checkable and checkStrictly is true, its object has checked and halfChecked property. Regardless of whether the child or parent treeNode is checked, they won't impact each other. |
string[] | {checked: string[], halfChecked: string[]} | [] | |
checkStrictly | Check treeNode precisely; parent treeNode and children treeNodes are not associated | boolean | false | |
defaultCheckedKeys | Specifies the keys of the default checked treeNodes | string[] | [] | |
defaultExpandAll | Whether to expand all treeNodes by default | boolean | false | |
defaultExpandedKeys | Specify the keys of the default expanded treeNodes | string[] | [] | |
defaultExpandParent | auto expand parent treeNodes when init | bool | true | |
defaultSelectedKeys | Specifies the keys of the default selected treeNodes | string[] | [] | |
disabled | whether disabled the tree | bool | false | |
draggable | Specifies whether this Tree is draggable (IE > 8) | boolean | false | |
expandedKeys | (Controlled) Specifies the keys of the expanded treeNodes | string[] | [] | |
filterTreeNode | Defines a function to filter (highlight) treeNodes. When the function returns true , the corresponding treeNode will be highlighted |
function(node) | - | |
loadData | Load data asynchronously | function(node) | - | |
loadedKeys | (Controlled) Set loaded tree nodes. Need work with loadData |
string[] | [] | |
multiple | Allows selecting multiple treeNodes | boolean | false | |
selectable | whether can be selected | boolean | true | |
selectedKeys | (Controlled) Specifies the keys of the selected treeNodes | string[] | - | |
showIcon | Shows the icon before a TreeNode's title. There is no default style; you must set a custom style for it if set to true |
boolean | false | |
switcherIcon | customize collapse/expand icon of tree node | ReactNode | - | |
showLine | Shows a connecting line | boolean | false | |
treeData | treeNodes data Array, if set it then you need not to construct children TreeNode. (key should be unique across the whole array) | array<{ key, title, children, [disabled, selectable] }> | - | |
virtual | Disable virtual scroll when set to false |
boolean | true | 4.1.0 |
onCheck | Callback function for when the onCheck event occurs | function(checkedKeys, e:{checked: bool, checkedNodes, node, event, halfCheckedKeys}) | - | |
onDragEnd | Callback function for when the onDragEnd event occurs | function({event, node}) | - | |
onDragEnter | Callback function for when the onDragEnter event occurs | function({event, node, expandedKeys}) | - | |
onDragLeave | Callback function for when the onDragLeave event occurs | function({event, node}) | - | |
onDragOver | Callback function for when the onDragOver event occurs | function({event, node}) | - | |
onDragStart | Callback function for when the onDragStart event occurs | function({event, node}) | - | |
onDrop | Callback function for when the onDrop event occurs | function({event, node, dragNode, dragNodesKeys}) | - | |
onExpand | Callback function for when a treeNode is expanded or collapsed | function(expandedKeys, {expanded: bool, node}) | - | |
onLoad | Callback function for when a treeNode is loaded | function(loadedKeys, {event, node}) | - | |
onRightClick | Callback function for when the user right clicks a treeNode | function({event, node}) | - | |
onSelect | Callback function for when the user clicks a treeNode | function(selectedKeys, e:{selected: bool, selectedNodes, node, event}) | - |
CHANGELOG
v4.1.6
2023-01-25
- Update
rc-tree
dependency
v4.1.5
2022-12-14
- (feat) Add
meta
toTreeWithDetails
props - (fix) Fix
<TreeWithDetails>
lazy loading with previously set expanded keys - (fix) Fix
addTreeMethodsBasedOnChildren
methods did not passmeta
to alldataProvider
methods - (fix) Fix
addTreeMethodsBasedOnParentAndPosition
methods did not passmeta
to alldataProvider
methods
v4.1.4
2022-11-21
- (fix) Fix default success side effect when creating a new node causes an error with RA version 4.4.0 and above
- (fix) Fix
<NodeActions>
should stop propagating the click event on close
v4.1.3
2022-09-29
- (fix) Fix moving child outside its parent with
addTreeMethodsBasedOnChildren
v4.1.2
2022-09-26
- (fix) Fix
addTreeMethodsBasedOnChildren
andaddTreeMethodsBasedOnParentAndPosition
not allowingmeta
to be omitted ingetTree
andgetRootNodes
v4.1.1
2022-09-19
- (fix) Fix
useMoveAsNthSiblingOf
query cache update when moving an item from top to bottom on the same branch
v4.1.0
2022-09-15
- (feat) Add support for meta in
ra-tree
hooks and components
v4.0.5
2022-09-08
- (fix) Fix
useEditNodeController
is not usingrecordRepresentation
- (fix) Fix
moveAsNthSiblingOf
reordering is wrong when source is below target inaddTreeMethodsBasedOnParentAndPosition
- (fix) Fix
moveAsNthSiblingOf
reordering is wrong when source is below target inaddTreeMethodsBasedOnChildren
- (fix) Fix moving at top position of the current branch with
moveAsNthChildOf
inaddTreeMethodsBasedOnChildren
- (doc) Improve documentation about
moveAsNthChildOf
andmoveAsNthSiblingOf
v4.0.4
2022-07-01
- (fix) UI corrections (alignement, width)
v4.0.3
2022-06-29
- Fix: Replace
classnames
withclsx
v4.0.2
2022-06-08
- (fix) Update peer dependencies ranges (support React 18)
v4.0.1
2022-06-08
- (fix)
<EditNode>
form child must take a custom toolbar to have the<DeleteButton>
delete a branch
v4.0.0
2022-06-07
- Upgraded to react-admin v4
- Upgraded to rc-tree v5.6.1
Breaking changes
The Signatures of all the DataProvider Hooks Have Changed
First, all hooks signatures now reflect their matching dataProvider method signature (so every hook now takes 3 arguments, resource
, params
and options
). The params
can also contain the newly supported meta
property. This property could be used to pass additional parameters not supported by default by our hooks and default dataProviders.
import { useGetTree } from '@react-admin/ra-tree';
const Categories = () => {
- const { data: tree, loading, loaded, error } = useGetTree('categories', options);
+ const { data: tree, isLoading, error } = useGetTree('categories', options);
- if (!loaded) { return <Loading />; }
+ if (isLoading) { return <Loading />; }
if (error) { return <p>ERROR</p>; }
return <Tree tree={data} />;
};
Second, the loading
property returned by these hooks has been renamed to isLoading
. The loaded
property has been removed.
import { useGetTree } from '@react-admin/ra-tree';
const Categories = () => {
- const { data: tree, loading, loaded, error } = useGetTree('categories');
+ const { data: tree, isLoading, error } = useGetTree('categories');
- if (!loaded) { return <Loading />; }
+ if (isLoading) { return <Loading />; }
if (error) { return <p>ERROR</p>; }
return <Tree tree={data} />;
};
Finally, these hooks are now powered by react-query, so the state
argument contains way more than just isLoading
(reset
, status
, refetch
, etc.). Check the useQuery and the useMutation documentation on the react-query website for more details.
onSuccess
And onFailure
Props Have Moved
If you need to override the success or failure side effects of a component, you now have to use the queryOptions
(for query side effects) or mutationOptions
(for mutation side effects).
For instance, here is how to customize the side effects on the deleteBranch
mutation in <DeleteMenuItem>
:
const MyDeleteMenuItem = () => {
const onSuccess = () => {
// do something
};
const onFailure = () => {
// do something
};
return (
<DeleteMenuItem
- onSuccess={onSuccess}
- onFailure={onFailure}
+ mutationOptions={{
+ onSuccess: onSuccess,
+ onError: onFailure
+ }}
/>
);
};
Note that the onFailure
prop was renamed to onError
in the options, to match the react-query convention.
The change concerns the following components:
useGetTree
useGetTreeCallback
useGetRootNodes
useGetChildNodesCallback
useAddRootNode
useAddChildNode
useDeleteBranch
useMoveAsNthChildOf
useMoveAsNthSiblingOf
useDeleteBranchWithConfirmController
useDeleteBranchWithUndoController
It also affects the save
callback returned by the useSaveContext
hook:
const MyButton = () => {
const { save, saving } = useSaveContext();
const notify = useNotify();
const handleClick = () => {
save({ value: 123 }, {
- onFailure: (error) => {
+ onError: (error) => {
notify(error.message, { type: 'error' });
},
});
};
return <button onClick={handleClick}>Save</button>
}
onSuccess
Callback On DataProvider Hooks And Components Has A New Signature
The onSuccess
callback used to receive the response from the dataProvider. On specialized hooks, it now receives the data
property of the response instead.
const [deleteBranch] = useDeleteBranch();
const DeleteBranchButton = () => {
const handleClick = () => {
deleteBranch(
'categories',
{ id: 123, data: { title: 'abc' } },
{
- onSuccess: ({ data }) => {
+ onSuccess: (data) => {
// do something with data
}
}
);
}
};
The change concerns the following components:
useGetTree
useGetTreeCallback
useGetRootNodes
useGetChildNodesCallback
useAddRootNode
useAddChildNode
useDeleteBranch
useMoveAsNthChildOf
useMoveAsNthSiblingOf
useDeleteBranchWithConfirmController
useDeleteBranchWithUndoController
<SimpleForm>
Isn't Exported Anymore, Use <EditNodeToolbar>
instead
In edition views, the previous version adised using an alternative <SimpleForm>
to replace the <DeleteButton>
by a version that deletes the current branch rather than the current node. In the new version, you have to use react-amin's <SimpleForm>
and pass a custom toolbar
instead:
import {
Admin,
Resource,
Create,
Edit,
+ SimpleForm,
TextInput,
} from 'react-admin';
import {
CreateNode,
EditNode,
+ EditNodeToolbar,
- SimpleForm,
TreeWithDetails,
} from '@react-admin/ra-tree';
const CategoriesEdit = () => (
<EditNode>
- <SimpleForm>
+ <SimpleForm toolbar={<EditNodeToolbar />}>
<TextInput source="title" />
</SimpleForm>
</EditNode>
);
<DeleteButton>
Was Renamed to <DeleteBranchButton>
To avoid conflicts, ra-tree
no longer exports a <DeleteButton>
commponent. Use <DeleteBranchButton>
instead.
import { Toolbar, ToolbarProps } from 'react-admin';
-import { DeleteButton } from '@react-admin/ra-tree';
+import { DeleteBranchButton } from '@react-admin/ra-tree';
import MyCustomButton from './MyCustomButton';
export const MyToolbar = (props: ToolbarProps) => (
<Toolbar>
<MyCustomButton />
- <DeleteButton />
+ <DeleteBranchButton />
</Toolbar>
);
v1.6.14
2021-12-22
- (feat) Add
addRootButton
toTreeWithDetails
to customize the add root button.
v1.6.13
2021-12-22
- (fix)
useGetRootNodes
doesn't return root nodes in the right order.
v1.6.12
2021-12-09
- (fix) allow Toolbar to override its children.
- (fix) deprecated material-ui symbols
v1.6.11
2021-11-10
- (fix) pass all props in every view from TreeWithDetails.
- (fix) ensure TreeWithDetails styles are overridable.
- (fix) ensure EditNode actions are overridable.
v1.6.10
2021-10-26
- (fix) builders now correctly apply their options (
childrenField
,parentField
, etc.)
v1.6.9
2021-10-18
- Update dataProvider usage for react-admin 3.19.0
v1.6.8
2021-09-21
- (fix) Fix TreeWithDetails props type doesn't include
classes
v1.6.7
2021-07-20
- (fix) Fix nodeActions are invisible
v1.6.6
2021-07-20
- (fix) Fix lazy loading support which required nodes to have their children identifiers populated
v1.6.5
2021-06-29
- (fix) Update peer dependencies ranges (support react 17)
v1.6.4
2021-06-15
- (fix) Fix
useGetRootNodes
anduseGetTree
are disabled by default.
v1.6.3
2021-05-03
- (fix) Fix scrollbars appearing when expanding nodes
v1.6.2
2021-05-03
- (fix) Fix reordering in the dataProviders builders.
v1.6.1
2021-04-26
- (performances) Replace MUI boxes by div with styles.
v1.6.0
2021-04-22
- (feat) Add lazy load mode on
<TreeWithDetails>
with thelazy
prop which accept a boolean.
In lazy mode, the tree will only load the root nodes on mount and will load the child nodes of a node when it expands.
v1.5.0
2021-04-02
- (feat) Allow to easily hide root nodes on
<TreeWithDetails>
with thehideRootNodes
props.
v1.4.1
2021-04-01
- (fix) Fix TreeWithDetails displays multiple titles after selecting a node
v1.4.0
2021-03-31
- (feat) Add support for title in
<TreeWithDetails>
- (feat) Fix and export more component props interfaces
v1.3.4
2021-03-17
- (fix) Fix moving a root node fails when using the addTreeMethodsBasedOnChildren builder
v1.3.3
2021-02-16
- (fix) Update for react-admin 3.12
v1.3.2
2020-12-14
- Remove unnecessary dependencies
v1.3.1
2020-11-18
- Upgrade to react-admin
3.10
v1.3.0
2020-10-05
- Upgrade to react-admin
3.9
v1.2.6
2020-09-30
- (fix) Ensure we don't prevent non tree records from being deleted
v1.2.5
2020-09-30
- Update Readme
v1.2.4
2020-09-28
- (fix) Fix warnings regarding React refs
- (fix) Fix warnings about unknown props in Toolbar
v1.2.3
2020-09-28
- (fix) Fix redirection side effect for DeleteMenuItem components
v1.2.2
2020-09-25
- (fix) Fix default redirect for DeleteMenuItem components
v1.2.1
2020-09-23
- (fix) Fix the style of the default
Toolbar
.
v1.2.0
2020-09-18
- (feat) Provides
SimpleForm
,TabbedForm
,Toolbar
andDeleteButton
components for use inside a view (Edit
andShow
for example).
v1.1.1
2020-09-17
- (fix) Fix Deletion components are not exported
- (fix) Fix TypeScript types for Deletion components props
- (fix) Fix Deletion components should have their redirect prop set to false by default
v1.1.0
2020-09-17
- (feat) Add support for branch deletion
v1.0.2
2020-09-16
- (deps) Upgrade dependencies
v1.0.1
2020-08-27
- (fix) Fix dark mode support
v1.0.0
2020-08-03
- First release