Material-table TypeError: Cannot add property tableData, object is not extensible
Asked Answered
F

5

20

I'm using meterial-table with React. I'm trying to assign data from an array coming from an api like this

<MaterialTable
  columns={columns}
  data={rows}
  ...
/>

Where columns and rows are api data. But I'm getting this error:

TypeError: Cannot add property tableData, object is not extensible

Notably When I use mock hard-coded data, things are working perfectly. After some search, I couldn't find any solution for it, any help?

Faro answered 8/1, 2020 at 14:54 Comment(2)
github.com/apollographql/react-apollo/issues/1251Harbor
related : #55567886 - and developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… , Found this thread as I am implementing material-table using redux, it works in my storybook story as I use the mocked data directly, but in development I use the mocked data via the redux store and got the same error.Nahtanha
E
26

This has nothing to do with material-table or React. Most probably this is related to your api response having Object.preventExtensions() applied on it for some reason, maybe this is an Axios behavior. So when material-table is trying to add an id field to each object, it's facing this error. Although not optimal, try to copy your api data to a new array of objects so material-table can modify them, e.g:

const editable = rows.map(o => ({ ...o }));
<MaterialTable
  columns={columns}
  data={editable}
  ...
/>

Note that I didn't use rows.map(o => o) as this will copy the array with the same objects references

EDIT: It's worth mentioning that using spread operator or Object.assign will only give a shallow copy, i.e. will not copy nested objects. One work-around for this is to use JSON.parse(JSON.stringify(object)). Please note that this would cause some data loss, other alternatives are on this answer: What is the most efficient way to deep clone an object in JavaScript?

Efflorescence answered 8/1, 2020 at 15:2 Comment(1)
worth noting if using this approach, "you only get shallow copies", it might be what you want, or it might not =) 2ality.com/2016/10/…Nahtanha
P
25

You are most likely using immer or a library that uses immer under the hood (like @reduxjs/toolkit). immer uses Object.freeze to make the objects it produces immutable.

material-table modifies its own props (which is a very ugly antipattern). When libraries break rules, they won't work with libraries that try to enforce them.

There is no way to unfreeze an object that has been frozen, but you have a couple of options:

  1. Find a way to disable freezing in the immer instance (check out the API docs of whatever you think might have frozen your state).

  2. Override Object.freeze making it do nothing (very hacky, should be avoided - and yet it might be your best shot here):

window.Object.freeze = function(obj) { return obj }
  1. Clone/deep copy your state before passing it to MaterialTable. This is also far from ideal, especially if you have a lot of data.
Pejsach answered 3/5, 2020 at 13:43 Comment(1)
Thank you very much for your suggestion/explanation. I had exactly the same problem using immer. Thankfully, immer indeed provides a functionality to disable freezing: import { setAutoFreeze } from 'immer'; setAutoFreeze(false);Shockheaded
L
3

import { setAutoFreeze } from 'immer'; setAutoFreeze(false);

worked for me. material table should consider an api that plays well with immer

Leede answered 23/9, 2020 at 18:57 Comment(2)
Thanks. Where do you call this if using Redux Toolkit?Interweave
you setAutoFreeze to false globally, or before you start using immerLeede
C
3

I got this error while passing the array data in the Material Data Table I was using reduxjs/toolkit

As the object are not modifiable ,due to internal implementation of Object.freeze() by

reduxjs/toolkit

 const {cycleList}=JSON.parse(JSON.stringify(useSelector(state=>state.cycleSlice))); 

I used above method to create a new copy of the object.

Conquian answered 27/9, 2021 at 7:2 Comment(1)
fwiw, you should put that: JSON.parse(JSON.stringify(useSelector(state=>state.cycleSlice))) into its own selector. That way it will be cached (memoized).Tussah
B
2

In my case Using "structuredClone" function solved this same issue.

var a = useSelector((state) =>state.cusLocChartTable.CusLocationCT);
const cloneData = structuredClone(a.billcity_table);

<ThemeProvider theme={defaultMaterialTheme}>
  <MaterialTable
       columns={[
               { title: 'City', field: 'city' },
               { title: 'Customers', field: 'customers' }
               ]}
               data={cloneData}
               title="Customers"
  />
</ThemeProvider>
Breaking answered 20/6, 2022 at 3:13 Comment(1)
The structuredClone() worked for me thanks.Plainspoken

© 2022 - 2024 — McMap. All rights reserved.