Our state of the art Gantt chart


Post by yuriv »

Hello,

I'm using Bryntum Gantt 4.0.1 and React 16.13.1.

Functional component renderers without hooks work just fine. But I'm using Redux and have to connect renderers to it.

I tried to use hooks:

function CellDate({ record, column }: BryntumCellProps) {
    const { dateFormat } = useSelector(selectUserData);
    return <CellDateCommon value={record?._data[column.field]} dateFormat={dateFormat} />;
}

export default CellDate;

I also tried connect hoc:

class CellDate extends React.Component<BryntumCellProps & any> {
    render() {
        const { record, column, dateFormat } = this.props;
        return <CellDateCommon value={record?._data[column.field]} dateFormat={dateFormat} />;
    }
}

export default connect((state: RootState) => ({
    dateFormat: selectUserData(state).dateFormat,
}))(CellDate);

See the results in attachment respectively:

redux-hooks.png
redux-hooks.png (53.88 KiB) Viewed 1252 times
redux-connect.png
redux-connect.png (34.07 KiB) Viewed 1252 times

Please, help.


Post by fabio.mazza »

Hi yuriv,

The error says basically that your column renderer - https://www.bryntum.com/docs/gantt/#Grid/column/Column#config-renderer (or defaultRenderer) is not a function, probably other value type. You can include a debugger on Row.js:645 to see what kind value you have for useRenderer.

As I don't see your columns or gantt configs is hard to tell exactly the problem can be. Could you please share a very simple test case with the error you are having? Thank you!

Best regards,
Fabio


Post by yuriv »

Fabio, thanks for clarifying that "is not a function" means "is not a function", I could never guess :)

It turned out that the error reproduces not with any hook. But with useSelector from react-redux it reproduces for sure. I'm attaching the clean test case, run it and look into console, please.

Attachments
gantt-sandbox.zip
(150.31 KiB) Downloaded 89 times

Post by saki »

What is actually happening here is that React does not find patterns that it needs to utilize a hook call. The React component/JSX cell rendering is implemented in the wrapper in processCellContent method:

processCellContent : ({cellContent, cellElement, cellElementData, record}) => {
    let shouldSetContent = cellContent != null;

    // Release any existing React component
    this.releaseReactCell(cellElement);

    // 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);
            cellElementData.reactPortal = portal;

... etc.

The solution could be to attempt to use a class based component (untested) but the best would probably be to pass the data required for rendering some other means. One of the reasons for that the React refuses to recognize functional component.

Another reason would be performance. We can have many, many instances of components as cell renderers each of going through overhead of being created and maintained by React plus each of them calling a hook. That could lead to performance degradation, I'm afraid.

The easiest and probably the most performant way to get data into the renderer is via the record that each one receives as input.


Post by saki »

What is actually happening here is that React does not find patterns that it needs to utilize a hook call.

This is what happens when the renderer is configured directly as React Functional Component. When it is wrapped in a function that returns JSX then hooks work as expected. We have updated the documentation with the following text:

If you use Functional React Components, wrap them in the renderer function that will return React FC, otherwise you will not be able to use React Hooks:

import React, {useEffect} from 'react';
const CellRenderer = ({value}) => {
    useEffect(()=>{
        console.log('Running useEffect');
    },[]);

    return <div>{value}</div>
}

// this will work
return (
    <BryntumGrid
        columns={[
            field: 'name',
            text: 'Name',
            renderer: ({value}) => <CellRenderer value={value} />
        ]}
    >
)

// this will NOT work
return (
    <BryntumGrid
        columns={[
            field: 'name',
            text: 'Name',
            renderer: CellRenderer
        ]}
    >
)

Post by yuriv »

Thank you Saki for the answer here and in the React portals topic. This is great that the issues are solved. Anyway, we decided to progress without React, as in our project Bryntum Gantt lives in quite isolated micro frontend and React's benefits seem not to justify the possible overhead.


Post Reply