Closed mukesh-1612 closed 1 week ago
import { useRef, useState } from 'react';
import { FaSquare, FaCheckSquare, FaMinusSquare } from 'react-icons/fa';
import { IoMdArrowDropright } from 'react-icons/io';
import { AiOutlineLoading } from 'react-icons/ai';
import TreeView from 'react-accessible-treeview';
import cx from 'classnames';
import './styles.css';
const initialData = [
{
name: '',
id: 0,
children: [1, 2, 3],
parent: null,
},
{
name: 'Fruits',
children: [],
id: 1,
parent: 0,
isBranch: true,
},
{
name: 'Drinks',
children: [4, 5],
id: 2,
parent: 0,
isBranch: true,
},
{
name: 'Vegetables',
children: [],
id: 3,
parent: 0,
isBranch: true,
},
{
name: 'Pine colada',
children: [],
id: 4,
parent: 2,
},
{
name: 'Water',
children: [],
id: 5,
parent: 2,
},
];
const getChildData = () => {
const arr = [];
for (let i = 0; i <= 5000; i++) {
const uid =
Date.now().toString(36) + Math.random().toString(36).substring(2);
arr.push({
name: 'Another child Node',
children: [],
id: uid,
parent: 3,
});
}
return arr;
};
function MultiSelectCheckboxAsync() {
const loadedAlertElement = useRef(null);
const [data, setData] = useState(initialData);
const [nodesAlreadyLoaded, setNodesAlreadyLoaded] = useState([]);
const updateTreeData = (list, id, children) => {
const data = list.map((node) => {
if (node.id === id) {
node.children = children.map((el) => {
return el.id;
});
}
return node;
});
return data.concat(children);
};
const onLoadData = ({ element }) => {
return new Promise((resolve) => {
if (element.children.length > 0) {
resolve();
return;
}
setData((value) => updateTreeData(value, element.id, getChildData()));
resolve();
});
};
const wrappedOnLoadData = async (props) => {
const nodeHasNoChildData = props.element.children.length === 0;
const nodeHasAlreadyBeenLoaded = nodesAlreadyLoaded.find(
(e) => e.id === props.element.id
);
await onLoadData(props);
if (nodeHasNoChildData && !nodeHasAlreadyBeenLoaded) {
const el = loadedAlertElement.current;
setNodesAlreadyLoaded([...nodesAlreadyLoaded, props.element]);
el && (el.innerHTML = `${props.element.name} loaded`);
// Clearing aria-live region so loaded node alerts no longer appear in DOM
setTimeout(() => {
el && (el.innerHTML = '');
}, 5000);
}
};
return (
<>
<div>
<div
className="visually-hidden"
ref={loadedAlertElement}
role="alert"
aria-live="polite"
></div>
<div
className="checkbox"
style={{
maxHeight: '80vh',
overflowY: 'scroll',
}}
>
<TreeView
data={data}
aria-label="Checkbox tree"
onLoadData={wrappedOnLoadData}
multiSelect
propagateSelect
togglableSelect
propagateSelectUpwards
nodeRenderer={({
element,
isBranch,
isExpanded,
isSelected,
isHalfSelected,
getNodeProps,
level,
handleSelect,
handleExpand,
}) => {
const branchNode = (isExpanded, element) => {
return isExpanded && element.children.length === 0 ? (
<>
<span
role="alert"
aria-live="assertive"
className="visually-hidden"
>
loading {element.name}
</span>
<AiOutlineLoading
aria-hidden={true}
className="loading-icon"
/>
</>
) : (
<ArrowIcon isOpen={isExpanded} />
);
};
return (
<div
{...getNodeProps({ onClick: handleExpand })}
style={{ marginLeft: 40 * (level - 1) }}
>
{isBranch && branchNode(isExpanded, element)}
<CheckBoxIcon
className="checkbox-icon"
onClick={(e) => {
handleSelect(e);
e.stopPropagation();
}}
variant={
isHalfSelected ? 'some' : isSelected ? 'all' : 'none'
}
/>
<span className="name">{element.name}</span>
</div>
);
}}
/>
</div>
</div>
</>
);
}
const ArrowIcon = ({ isOpen, className }) => {
const baseClass = 'arrow';
const classes = cx(
baseClass,
{ [`${baseClass}--closed`]: !isOpen },
{ [`${baseClass}--open`]: isOpen },
className
);
return <IoMdArrowDropright className={classes} />;
};
const CheckBoxIcon = ({ variant, ...rest }) => {
switch (variant) {
case 'all':
return <FaCheckSquare {...rest} />;
case 'none':
return <FaSquare {...rest} />;
case 'some':
return <FaMinusSquare {...rest} />;
default:
return null;
}
};
export default MultiSelectCheckboxAsync;
Loading any page with thousands of checkboxes would have performance issues. Any type of control actually (select, input). That's an extreme amount that I see no legitimate reason to expect to work in our library or even in standard HTML. A user is not going to look at 4000 elements and shouldn't even presented with that many.
I would advise adding some filtering before rendering a tree to reduce the number of nodes to a reasonable amount.
Could you implement react-window virtualization technique to this package?
@KrishEnacton that’s out of scope for this library. You’re welcome to fork if that’s what you want to do. Can you explain what use case could possibly benefit from 4000+ nodes?
btw, the library supports lazy loading.
just to be clear, this ticket is more of a “StackOverflow” styled question which we make clear in our issue creation flow that we don’t respond to. So don’t be surprised if responses to this thread slow down.
https://github.com/user-attachments/assets/c6bac325-04d4-48fe-ae3f-99a7465494ff
This is the use case where we want to select lessons in a course. There may not be 2000+ nodes in a single course, but with 10 courses, each having 500 lessons, it is possible to reach that number.
We have implemented this, but we were not aware of the performance issue, as we thought virtualization had been handled. I have attached the video of the use case.
As you can see, the data is fetched asynchronously, but the content loads very slowly, and the selections are delayed. Other than that, the package is really awesome and easy to integrate with our code.
Here are some solutions and then after this we won’t be giving free advice anymore for our volunteer based open source library.
Ultimately a tree is not the best use case for what you’ve shown us.
We might lock this thread in the future. Please consider our recommendations as your request is not in scope for this library and won’t be done.
When loading a tree with 4000 children, selecting checkboxes becomes very slow. The performance seems to degrade due to the rendering of a large number of nodes, causing a noticeable delay when selecting or deselecting a checkbox.
Steps to Reproduce:
Create a tree with around 4000 children. Attempt to select a checkbox for one of the tree nodes. Observe the delay in response when the checkbox is selected or deselected.
Expected Behavior Checkbox selection should be fast and responsive, even with a large number of tree nodes.
Actual Behavior There is a significant delay in checkbox selection when dealing with 4000 children, which seems related to rendering performance.
Possible Solutions Is there a recommended solution to handle such a large number of children efficiently?