Our blazing fast Grid component built with pure JavaScript


Post by Yamo1993 »

After upgrading the Bryntum Grid React version from 4.1.1 to 4.1.6, I'm getting this error:

Uncaught TypeError: Cannot add property parentId, object is not extensible
    at eval (grid.umd.js?0469:17021)
    at _loop (grid.umd.js?0469:39771)
    at Function.preWalkWithParent (grid.umd.js?0469:39785)
    at Store.syncTreeDataset (grid.umd.js?0469:16998)
    at Store.syncDataset (grid.umd.js?0469:16838)
    at Store.setStoreData (grid.umd.js?0469:9419)
    at Store.set (grid.umd.js?0469:9323)
    at Grid.set (grid.umd.js?0469:124420)
    at eval (WrapperHelper.js?b7f3:599)
    at Array.forEach (<anonymous>)

This is where it is failing:
Image

Can someone please tell me why this is happening? And is there any quick workaround for this?


Post by mats »

We could not reproduce this issue. To assist you with your question, please provide more details including a runnable test case (as described in our support guidelines).

To get the fastest possible support, provide a simple test case based on our standard examples.


Post by Yamo1993 »

I will try to provide some more details and a test case. So, the data that I am working with is a Mobx State Tree array with frozen objects (https://mobx-state-tree.js.org/API/#frozen).

I checked if the individual items in that MST array are frozen with Object.isFrozen() and it returned true.

The interesting thing here is that the grid worked fine in version 4.1.1. In version 4.1.6 though, it suddenly throws errors if the array has frozen objects. Nothing has changed with my dataset. The grid just behaves differently when handling it.

My suspicion here is that maybe in the older version, the grid instance cloned the array before adding a parentId to it before, thus removing the problem. My quick workaround for now is to simply make a deep clone of the array before rendering the grid, but I'm still curious to know why this suddenly doesn't work anymore.

Last edited by Yamo1993 on Wed Jun 30, 2021 9:51 am, edited 4 times in total.

Post by Yamo1993 »

Here is also a running test case. Things to note:

  • The test case uses a Mobx State Tree model with an array of objects with the MST type "frozen".
  • The grid used to work with this identical dataset in version 4.1.1.
  • The imported Grid component is just a wrapper around the BryntumGrid React component.

prototype.ts:

import Grid from 'js/components/grid/grid';

import React, { ReactElement, useEffect, useRef, useState } from 'react';
import { types, cast } from 'mobx-state-tree';
import { observer } from 'mobx-react';

const PrototypingModel = types.model({
    records: types.array(types.frozen()),
})
.actions(self => {
    function setRecords (records: any[]): void {
        self.records = cast(records);
    }

function initiateModel (): any[] {
    const records = [
        {
            children: [
                {
                    responsibilities: [],
                    randomId: 18770,
                    title: 'Child title',
                },
            ],
            responsibilities: [],
            randomId: 18769,
            title: 'Parent title',
        },
    ];
    setRecords(records);
    return records;
}

return {
    setRecords,
    initiateModel,
};
})
.views(self => {
    function getRecords (): any[] {
        return self.records.toJSON();
    }

return {
    getRecords,
};
});

const PrototypingModelDefaults = {
    records: [],
};

const model = PrototypingModel.create(PrototypingModelDefaults);

const Prototyping = observer((): ReactElement => {
    const [gridData, setGridData] = useState([]);

const grid = useRef(null);

useEffect(() => {
    const records = model.initiateModel();
    setGridData(records);
}, []);

useEffect(() => {
    const records = model.getRecords();
    setGridData(records);
}, [model.getRecords()]);

return (
    <Grid
        ref={grid}
        columns={[
            {
                field: 'title',
                text: 'Title',
                type: 'tree',
                editor: false,
                flex: 1,
            },
            {
                field: 'responsibilities',
                text: 'Responsibilities',
                flex: 1,
                htmlEncode: false,
                renderer: (): ReactElement => {
                    return (
                        <p>Test</p>
                    );
                },
                editor: false,
            },
            {
                type: 'action',
                text: '',
                width: 60,
                align: 'center',
                field: 'action',
                actions: [{
                    cls: 'b-fa b-fa-plus',
                    renderer: ({ action }): string => `<i class="b-action-item ${action.cls}"></i>`,
                    visible: true,
                    tooltip: ({ record }): string => `<p class="b-nicer-than-default">Add to ${record.title} <title></p>`,
                }],
            },
        ]}
        data={gridData}
        treeFeature
        rowHeight={32}
    />
);
});

export default Prototyping;

Post by saki »

Could you provide a runnable showcase that we can drop in our examples folder, run and debug? Including the data used to reproduce the issue.

Meanwhile, you should use the provided wrapper from @bryntum/grid-react package.


Post by Yamo1993 »

import { BryntumGrid } from '@bryntum/grid-react';

import React, { ReactElement, useEffect, useState } from 'react';
import { types, cast } from 'mobx-state-tree';
import { observer } from 'mobx-react';

const PrototypingModel = types.model({
    records: types.array(types.frozen()),
})
    .actions(self => {
        function setRecords (records: any[]): void {
            self.records = cast(records);
        }

    function initiateModel (): any[] {
        const records = [
            {
                children: [
                    {
                        responsibilities: [],
                        randomId: 18770,
                        title: 'Child title',
                    },
                ],
                responsibilities: [],
                randomId: 18769,
                title: 'Parent title',
            },
        ];
        setRecords(records);
        return records;
    }

    return {
        setRecords,
        initiateModel,
    };
})
.views(self => {
    function getRecords (): any[] {
        return self.records.toJSON();
    }

    return {
        getRecords,
    };
});

const PrototypingModelDefaults = {
    records: [],
};

const model = PrototypingModel.create(PrototypingModelDefaults);

const Prototyping = observer((): ReactElement => {
    const [gridData, setGridData] = useState([]);

useEffect(() => {
    const records = model.initiateModel();
    setGridData(records);
}, []);

useEffect(() => {
    const records = model.getRecords();
    setGridData(records);
}, [model.getRecords()]);

return (
    <BryntumGrid
        columns={[
            {
                field: 'title',
                text: 'Title',
                type: 'tree',
                editor: false,
                flex: 1,
            },
            {
                field: 'responsibilities',
                text: 'Responsibilities',
                flex: 1,
                htmlEncode: false,
                renderer: (): ReactElement => {
                    return (
                        <p>Test</p>
                    );
                },
                editor: false,
            },
            {
                type: 'action',
                text: '',
                width: 60,
                align: 'center',
                field: 'action',
                actions: [{
                    cls: 'b-fa b-fa-plus',
                    renderer: ({ action }): string => `<i class="b-action-item ${action.cls}"></i>`,
                    visible: true,
                    tooltip: ({ record }): string => `<p class="b-nicer-than-default">Add to ${record.title} <title></p>`,
                }],
            },
        ]}
        data={gridData}
        treeFeature
        rowHeight={32}
    />
);
});

export default Prototyping;

The showcase is already runnable. But it uses the mobx-state-tree and mobx-react packages. You should be able to test the grid with external packages too, not just drop it in an example sandbox, unless your sandbox is clever enough to install needed packages.

The data is provided in the code already. The data has a tree structure.

I changed the import to the provided wrapper, the behavior is still the same.


Post by mats »

I don't believe we ever intended to support loading frozen data objects, looks like your MST is freezing the objects you pass in to our data stores, correct? Any chance you can opt out of this?


Post by Yamo1993 »

I see. I guess you unintentionally supported it in a previous version, haha :)

I will see what I can do with the MST model. The easy workaround is to make a deep clone, but it's not that performant.

Thanks for your answer though.


Post Reply