An editable table with React, Material UI and React Virtualized

Matthew Cox
Guestline Labs
Published in
7 min readNov 9, 2021

The core feature of one of our applications at Guestline is to show complex tabular data. We use Material UI extensively, so naturally our first iteration leveraged their DataGrid component. We soon found we needed to circumvent certain limitations imposed on the free tier version of DataGrid, and in doing so our code became quite complex. We ended up abandoning it entirely in favour of a simpler, virtualized solution using React Virtualized. This blog post documents our challenges with DataGrid, how we implemented our own improved alternative and the lessons we learned along the way.

Demo Code

I have written an example project that compares DataGrid, a plain HTML table, and React Virtualized. You can find it here: https://github.com/Matt-TOTW/Virtualized-editable-data. The examples below are all adapted from this project.

Part 1: Our use case

We needed multiple grids, of differing structures/schemas that could present the data attractively and be able to

  • sort the data
  • edit the data
  • cope with large amounts of data
  • preferably not paginate the data

We quickly decided on DataGrid from Material UI. We noticed that there was a paid tier for this component — DataGridPro — but after a brief look at the capabilities of DataGrid, we were confident it would be able to deal with all of the above requirements.

Getting it going is pretty straightforward.

Result:

There are lots of props available on <DataGrid>. Aside from the necessary rows and columns, I’ve just added autoHeight, for styling purposes, and also disableSelectionOnClick, which disables some unwanted default behaviour. The GridColDef interface includes editable, which is a good example of some out-of-the-box functionality provided by DataGrid. Based on this prop, DataGrid allows for the data to be displayed in edit mode, which by default will use inputs to display the data.

Any edits to the grid are stored internally by DataGrid and an API is provided to access the up-to-date data. Also defined on GridColDef are renderCell and renderEditCell, which are methods that allow us to overwrite the default rendering behaviour on a cell by cell basis and it is here that we have access to the API.

Let’s look at an example. By default, cells given the editable prop can be edited by double-clicking them. Instead of going cell by cell, we want to click an edit button located at the end of each row which will convert the entire row to edit mode.

Like this:

To do that, we’ll define another column with a custom renderCell method.

The interface GridRenderCellParams defines a parameter called api, which is of course the API mentioned earlier. One of the available methods on this API is setRowMode and it’s this that does the job we want. This API has a lot of things available. It effectively completely exposes not just the internalised data but also a whole host of methods for manipulating the general state of the grid. As shown above, this API is available to us when rendering the cell, but we quickly found we wanted access to it at the grid component level. We were expecting there to be a reference to it available as a prop on <DataGrid> but unfortunately, this reference prop is not available on the free tier version. It is called apiRef and it is only available on DataGridPro. It does exactly what you’d expect, it is a reference object to the API and it would be used by assigning it to a React ref. Not having it available turned out to be the first of several problems.

Part 2: Problems with DataGrid

Our project grew in size. We added more grids, with increasingly complicated schemas. We began to add more features to our grids, like searching, sorting, and grouping to name a few.

I won’t go into all the ways we became unstuck with DataGrid but suffice it to say our code became quite complex. Our implementation needed to make extensive use of the API but because it was not available to us everywhere, we had to do so in quite creative ways. Each new workaround added to the code complexity and by the end we were using so many that we were no longer utilising the real power of DataGrid — rich out of the box features.

The final nail in the coffin was when we started looking at large data sets. DataGrid will automatically page your data at 100 rows and there is nothing you can do about it. This is actually a sensible annoyance as it protects your browser from very heavy rendering work which can easily freeze the grid, especially if your row data is not simple in structure. DataGridPro on the other hand allows for paging at any number of rows or no paging at all. To alleviate the rendering workload this is accompanied by data virtualisation, enabled by default and unavailable on DataGrid, of which more later.

The combination of these factors:

  • high code complexity
  • necessarily poor utilisation of DataGrid’s features
  • forced paging at 100 rows / no virtualisation

meant we needed to find an alternative.

Why not DataGridPro?
Their pricing model does not fit well into our labs; it is done per developer seat and not, for example, per implementation. Guestline is a small enterprise, we don’t have a procurement team to manage licences etc. It is also not cheap and it was therefore quite easy to justify the time needed to investigate some open source alternatives. Lastly, regardless of features or cost, we were keen to implement a solution more natural to the React way of doing things, one that avoids obfuscated internalised state.

Part 3: React Virtualized

We ended up throwing a lot of work away but the result was a simpler, far more performant solution using React Virtualized. Also, there are some familiar ideas, most notably that of a cellRenderer which we use extensively.

React Virtualized provides us with <Table> and <Column> components, <Column>s will be children of <Table>. The central idea is that we provide <Table> with a rowGetter function. This is used to populate the table with the rows, but only the rows that are scrolled into view on the viewport. In this way, the browser is only rendering/painting a small subset of the total number of rows.

A basic implementation looks like this:

You can see that the rowGetter is just a very simple function that given an index returns the row.

React Virtualized doesn’t internalise the data like DataGrid does, so our bespoke solution will keep the data in component state and any edits will simply update this state — a far more natural React experience.

Before we get to the final code, a quick note on sizing and the <AutoSizer> component provided by React Virtualized. <Table> requires explicit, numeric dimensions (not css styles) and <AutoSizer>provides them. It will expand to fill its parent element and be aware of its own dimensions, which it can then pass to its child — in our case <Table>. We just need to make sure that the immediate parent of <AutoSizer> has non-zero height and width. We do this by either setting them explicitly (as we have done above) or using a CSS rule like flex: 1 or height: 100%.

Let’s make the following improvements.

  • As before, let’s use an icon at the end of each row to enter edit mode. DataGrid came with edit mode, but now with React Virtualized this is something we need to implement ourselves. Let’s add an onClick handler to the edit icon that adds isEdit: true to the row object. Then, if the row object has isEdit we’ll display inputs, otherwise we’ll just display normal TableCells.
  • The inputs will have onChange handlers that directly update the rows state so that the rowGetter will always return the correct data to <Table>.
  • To do this we’ll need to use cellRenderers, and let’s also use a headerRenderer to render the header row cells.
  • Lastly, instead of repeatedly writing out multiple <Columns>s let’s instead define our columns in an array and then loop through the array to return each <Column>.

I’ve also added getRowStyles (to keep things looking visually similar to the DataGrid table) and a custom <TableCell> component to reduce duplication.

And there we have it — a table capable of rendering tens of thousands of rows, fully editable.

Summary

We wanted to display large amounts of tabular data. We looked at Material UI’s powerful DataGrid component but bumped into some limitations of its free tier version that caused us some problems. Specifically:

  • The internal API is not available everywhere we wanted it and this made for some complex code as we came up with workarounds.
  • These workarounds meant we were no longer utilising the rich features of DataGrid as we were largely implementing our own solutions.
  • The automatic and enforced paging at 100 rows was a problem for us.

We then used React Virtualized to create the grid instead, leveraging its <Table> component to render a subset of the total rows and using custom cellRenderers and component state to create the editable content we needed.

Lessons learned

It’s never too late to step back and reevaluate the core requirements of a project, and starting again may in fact be much less arduous than expected. There are likely to be lots of transferrable concepts from one to the next, and much of the hard work may well have already been done.

Photo by Tim Mossholder on Unsplash

Join us at Guestline

We are always on the lookout for talented new engineers and developers. Have a look at our careers page for the latest job openings.

--

--

Matthew Cox
Guestline Labs

Junior developer at Guestline, surprisingly good on roller skates.