After implementing React cell renderers I noticed serious performance drop. See test case attached. Reproduction steps:
1) yarn && yarn start
2) Don't open Chrome devtools! With open Chrome devtools browser window may deadly freeze, so better try it first without active devtools.
3) Expand few tasks to get more rows visible and scroll back and forth, you will experience heavy UI freezes.
Despite the problems with devtools I managed to use React profiler and made a screenshot. After one single task expanded (with just few sub tasks) BryntumGantt component rendered 192 times (see screenshot attached). This seems to be the root of the issue.
In production build things become a little better, but freezes are still very perceptible. And devtools still makes dead freeze.
Please, help.
Attachments
insane-rerenders.png (53.97 KiB) Viewed 1724 times
I reviewed it briefly and as I can see the root issue is related to Bryntum internal logic which I'm not familiar with. If I'm wrong, please enlighten me.
I have analyzed the performance with using you test case and indeed that many React components do not render fast. What is going on under the hoods is that the wrapper creates React portals as needed and releases (deletes) them to cleanup when not needed anymore.
// Hook called by engine when rendering cells, creates portals for JSX supplied by renderers
processCellContent : ({cellContent, cellElement, cellElementData, record}) => {
let shouldSetContent = cellContent != null;
// Release any existing React component
this.releaseReactCell(cellElement); // <===== here we delete the portal
// Detect React component
if (cellContent && cellContent.$$typeof === Symbol.for('react.element')) {
// Excluding special rows for now to keep renderers simpler
if (!record.meta.specialRow) {
// Clear any non-react content
const firstChild = cellElement.firstChild;
if (!cellElementData.reactPortal && firstChild) {
firstChild.data = '';
}
// Create a portal, belonging to the existing React tree but render in a cell
const portal = ReactDOM.createPortal(cellContent, cellElement); // <==== here we create portal
cellElementData.reactPortal = portal;
// ... etc
releaseReactCell(cellElement) {
const
{state} = this,
cellElementData = cellElement._domData;
// Cell already has a react component in it, remove
if (cellElementData.reactPortal) {
state.portals.delete(cellElementData.reactPortal);
this.setState({
portals : state.portals,
generation : state.generation + 1
});
cellElementData.reactPortal = null;
}
}
The create/delete portal cycle shown to be the bottleneck also in the view of fact that it runs also when scrolling cells in/out of view.
I have made preliminary tests of another approach where we cache the created portals and re-use them and the results look promising.
FYI, the issue has been resolved. We have introduced a config option discardPortals that is set to false by default. The rendering engine now keeps once created portals and only hides them as necessary. This saves DOM manipulation after the first portals creation so the performance is very good.
In the cases when the memory consumption is of priority, this option can be set true. In this case the portals are deleted when not needed (on scrolling, for example).
Note: The solution is not yet merged and has been slated for version 4.0.4.
We decided to give it a try and to make new POC with React. Unfortunately, with React renderers I still see very noticeable lags during cells first initialization and also when scrolling the grid even after all cells are initialized. App this way becomes barely usable.
In my grid I have 10 columns and few hundreds rows.
Don't you have an example with React, big dataset and 10+ columns with React renderers in the grid? As I see, all examples just have react wrapper, but don't massively use react renderers.
Until you don't have such an example and according to our struggle I should declare that Bryntum Gantt doesn't seem to me really compatible with React.