Why memory leak happend and render slowing down when I close and re-open react component (material-table)?
Asked Answered
E

3

7

I have basic react example for learning and I use material-table in one of my components. Each time I change the page and re-open it (unmount and mount component), my component which contains material-table load more slowly. I share my code below.


import MaterialTable from 'material-table';

const columns = [
    { title: 'Id', field: 'id', hidden: true },
    { title: 'Username', field: 'username' },
    { title: 'Name', field: 'name' },
    { title: 'Phone', field: 'phone'}
];

const tableData = [
    {
        id: 1,
        username: "User-1",
        name: "name-1",
        phone: "555 444 33 22"
    },
    {
        id: 2,
        username: "User-2",
        name: "name-2",
        phone: "111 222 33 44"
    },
    {
        id: 3,
        username: "User-3",
        name: "name-3",
        phone: "999 999 99 99"
    }
];

const MTable = () => {
    return (
        <MaterialTable 
            title="Basic Search Preview"
            columns={columns}
            data={tableData}
            options={{search: true }}
        />
    )
}

export default MTable


After long search I did not find any solution, and after long try I just change the place of columns definition like below.


const MTable = () => {

    const columns = [
        { title: 'Id', field: 'id', hidden: true },
        { title: 'Username', field: 'username' },
        { title: 'Name', field: 'name' },
        { title: 'Phone', field: 'phone'}
    ];

    return (
        <MaterialTable 
            title="Basic Search Preview"
            columns={columns}
            data={tableData}
            options={{search: true }}
        />
    )
}

This change solve my problem but I really want to learn why this happened. When I made the column definition outside of the method why memory leak and render slowed each page change. At the same time, when I moved into method what changed?

Elias answered 16/9, 2021 at 20:13 Comment(4)
this doesn't make any sense, actually the second version should be in theory slower :) btw slow <> memory leak, there must be something else which caused this.Makalu
Actually I thougth the same way. That is why, finding the solution took my long time. You can basically try two version of this and you will see the problem. Each time you unmount and mount the component, It will take more time. @MakaluElias
@Makalu In addition, Its really ate my all memory :)Elias
I do confirm this behavior. When mounting / unmounting repeatedly with e.g. { !show ? null : <MTable /> } it took approx. 50, 70, 130, 870, 8020 milliseconds to build the MaterialTable.Telegraphic
T
6

analysis

material-table adds a property column.tableData to each column of your columns list, and then there is an assignment that effectively does something like (see file data-manager.js):

column[0].tableData.width = "calc(" + ... +
  column[0].tableData.width + ... +
  column[1].tableData.width + ... + ")"

column[1].tableData.width = "calc(" + ... 
...

Because the columns are in the global scope and are not destroyed on every unmount, this lets the string tableData.width grow exponentially. I guess the increasingly long time it takes comes from these increasingly many nested "calc()" invocations.

conclusion

I hesitate to call this a bug in material-table.

It looks like material-table expects the columns to be created on every render (and not to be persistent). Fair enough, but I would at least call this unexpected behavior for somebody who is used to work in React, and there should be a warning about this in the documentation. I also think even then that could have been implemented foolproof. (if somebody disagrees, I'd like to read reasons in the comments)

example

The first time the component is mounted the tableData.width is:

calc((100% - (0px +
  calc((100% - (0px)) / 3) +
  calc((100% - (0px)) / 3) +
  calc((100% - (0px)) / 3)
)) / 3)

After un-mount and a second mount the width tableData.width is:

calc((100% - (0px +
  calc((100% - (0px + 
    calc((100% - (0px + calc((100% - (0px)) / 3) + calc((100% - (0px)) / 3) + calc((100% - (0px)) / 3))) / 3) + 
    calc((100% - (0px + calc((100% - (0px)) / 3) + calc((100% - (0px)) / 3) + calc((100% - (0px)) / 3))) / 3) + 
    calc((100% - (0px + calc((100% - (0px)) / 3) + calc((100% - (0px)) / 3) + calc((100% - (0px)) / 3))) / 3)
  )) / 3) +
  calc((100% - (0px + 
    calc((100% - (0px + calc((100% - (0px)) / 3) + calc((100% - (0px)) / 3) + calc((100% - (0px)) / 3))) / 3) + 
    calc((100% - (0px + calc((100% - (0px)) / 3) + calc((100% - (0px)) / 3) + calc((100% - (0px)) / 3))) / 3) + 
    calc((100% - (0px + calc((100% - (0px)) / 3) + calc((100% - (0px)) / 3) + calc((100% - (0px)) / 3))) / 3)
  )) / 3) +
  calc((100% - (0px + 
    calc((100% - (0px + calc((100% - (0px)) / 3) + calc((100% - (0px)) / 3) + calc((100% - (0px)) / 3))) / 3) + 
    calc((100% - (0px + calc((100% - (0px)) / 3) + calc((100% - (0px)) / 3) + calc((100% - (0px)) / 3))) / 3) + 
    calc((100% - (0px + calc((100% - (0px)) / 3) + calc((100% - (0px)) / 3) + calc((100% - (0px)) / 3))) / 3)
  )) / 3)
)) / 3)"
Telegraphic answered 18/9, 2021 at 1:4 Comment(2)
What an amazing find. This is absolutely a bug in material-table. I mean, first rule of react right, don't mutate stuff.Protochordate
I opened an issue (bug) to material table about it. (github.com/mbrn/material-table/issues/3018)Elias
I
0

The columns array should be static to a material table instance. When it is dynamically called again and again, the memory fills and finally runs out of space.

Icterus answered 5/12, 2021 at 13:38 Comment(0)
D
0

If you try to use useState to manage the columns this issue will happen. I had a workaround where I would set the columns each time which worked but would reset my groupings, etc.

There is a fork of material-table which solves that issue:https://material-table-core.com/docs/#installation

npx create-react-app material-table-app
npm uninstall react react-dom
npm install [email protected] [email protected] 
npm install @material-table/[email protected] --legacy-deps 

This is the combination that worked for me. Using this setup I can use useState to set my columns/maintain their state.

I was getting a _deepmerge issue with material-table ^5.0 that I couldn't resolve. Almost just gave up entirely on this package.

Devoice answered 25/11, 2022 at 0:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.