Building and styling tables with react-table v7
Tags: react, tutorial
Published: 2020-04-29 14:00:58

---
title: Building and styling tables with react-table v7
published: true
date: 2020-04-02 13:00:21 UTC
tags: react,tutorial
canonical_url: https://blog.logrocket.com/building-styling-tables-react-table-v7/
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/3ji8esfpwqvr2nu4n51v.jpeg
---
**Written by [John Au-Yeung](https://blog.logrocket.com/author/johnau-yeung/)**✏️

It’s widely acknowledged that creating a table with React is a pain. No surprise, then, that there are many libraries to make creating tables easier for React apps.

One of these packages that aims to make our lives easier is react-table. It provides a modern, Hooks-based API to let us create tables with React with little hassle.

In this article, we’ll look at how to use react-table to add tables to our React app.

## Creating a basic table

Creating a basic table in a React app is easy with react-table. Run the following to install it:

```jsx
npm i react-table
```

Then we can use it as follows:

```jsx
import React from "react";
import { useTable } from "react-table";

const data = [
  { firstName: "jane", lastName: "doe", age: 20 },
  { firstName: "john", lastName: "smith", age: 21 }
];

const columns = [
  {
    Header: "Name",
    columns: [
      {
        Header: "First Name",
        accessor: "firstName"
      },
      {
        Header: "Last Name",
        accessor: "lastName"
      }
    ]
  },
  {
    Header: "Other Info",
    columns: [
      {
        Header: "Age",
        accessor: "age"
      }
    ]
  }
];

const Table = ({ columns, data }) => {
  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    rows,
    prepareRow
  } = useTable({
    columns,
    data
  });

  return (
    <table {...getTableProps()}>
      <thead>
        {headerGroups.map(headerGroup => (
          <tr {...headerGroup.getHeaderGroupProps()}>
            {headerGroup.headers.map(column => (
              <th {...column.getHeaderProps()}>{column.render("Header")}</th>
            ))}
          </tr>
        ))}
      </thead>
      <tbody {...getTableBodyProps()}>
        {rows.map((row, i) => {
          prepareRow(row);
          return (
            <tr {...row.getRowProps()}>
              {row.cells.map(cell => {
                return <td {...cell.getCellProps()}>{cell.render("Cell")}</td>;
              })}
            </tr>
          );
        })}
      </tbody>
    </table>
  );
};

export default function App() {
  return (
    <div className="App">
      <Table columns={columns} data={data} />
    </div>
  );
}
```

In the code above, we imported the `useTable` Hook from the react-table package. Then we created the `data` to populate the table with data:

```jsx
const data = [
  { firstName: "jane", lastName: "doe", age: 20 },
  { firstName: "john", lastName: "smith", age: 21 }
];
```

We just put properties in objects to add additional data for a table row.

[![LogRocket Free Trial Banner](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/f760c-1gpjapknnuyhu8esa3z0jga.png?resize=1200%2C280&ssl=1)](https://logrocket.com/signup/)

We can create columns in a list with the following code:

```jsx
const columns = [
  {
    Header: "Name",
    columns: [
      {
        Header: "First Name",
        accessor: "firstName"
      },
      {
        Header: "Last Name",
        accessor: "lastName"
      }
    ]
  },
  {
    Header: "Other Info",
    columns: [
      {
        Header: "Age",
        accessor: "age"
      }
    ]
  }
];
```

The `Header` property has the string for the names that’ll be displayed, and the `accessor` property is the property name that’s in the array entry objects.

In the `Table` component code, we have:

```jsx
const Table = ({ columns, data }) => {
  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    rows,
    prepareRow
  } = useTable({
    columns,
    data
  });

  return (
    <table {...getTableProps()}>
      <thead>
        {headerGroups.map(headerGroup => (
          <tr {...headerGroup.getHeaderGroupProps()}>
            {headerGroup.headers.map(column => (
              <th {...column.getHeaderProps()}>{column.render("Header")}</th>
            ))}
          </tr>
        ))}
      </thead>
      <tbody {...getTableBodyProps()}>
        {rows.map((row, i) => {
          prepareRow(row);
          return (
            <tr {...row.getRowProps()}>
              {row.cells.map(cell => {
                return <td {...cell.getCellProps()}>{cell.render("Cell")}</td>;
              })}
            </tr>
          );
        })}
      </tbody>
    </table>
  );
};
```

The `useTable` Hook takes the `column` and `data` from the props, which originate from those objects and arrays that we defined earlier. We get the functions from the `getTableProps` and `getTableBodyProps` from the object returned from the `useTable` Hook.

The `getHeaderProps()` function is called inside the `th` tags and spread to populate the headers. With this, we pass the props returned by the `getTableBodyProps()` function to `tbody` to spread the props to properly style and align the columns.

The `prepareRow(row);` returned from the `useTable` Hook creates the row entries, which can be automatically populated after the call to the function changes the `row` object in place.

Then we have:

```jsx
{rows.map((row, i) => {
  prepareRow(row);
  return (
   <tr {...row.getRowProps()}>
     {row.cells.map(cell => {
       return <td {...cell.getCellProps()}>{cell.render("Cell")}</td>;
     })}
   </tr>
  );
})}
```

This automatically populates the cells by getting the items from the `getCellProps()` method and then populating the values from the returned object. We called `cell.render("Cell")` to render each `td` as a cell.

Finally, in `App`, we used the `Table` component, which takes the `column` and `data` props. The values are the `columns` and `data` objects that we created earlier.

The items displayed in the table in two panes. The left pane has the **Name**  header with two columns: **First Name** and **Last Name**. Then, the right pane has the **Other Info** heading with the **Age**  column.

## Adding a footer

We can add a footer by adding a `Footer` property to out column objects. We can write the following code to do that:

```jsx
import React from "react";
import { useTable } from "react-table";

const data = [
  { firstName: "jane", lastName: "doe", age: 20 },
  { firstName: "john", lastName: "smith", age: 21 }
];

const columns = [
  {
    Header: "Name",
    Footer: "Name",
    columns: [
      {
        Header: "First Name",
        accessor: "firstName"
      },
      {
        Header: "Last Name",
        accessor: "lastName"
      }
    ]
  },
  {
    Header: "Other Info",
    Footer: "Other Info",
    columns: [
      {
        Header: "Age",
        accessor: "age",
        Footer: info => {
          const total = React.useMemo(
            () => info.rows.reduce((sum, row) => row.values.age + sum, 0),
            [info.rows]
          );

          return <>Average Age: {total / info.rows.length}</>;
        }
      }
    ]
  }
];

const Table = ({ columns, data }) => {
  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    footerGroups,
    rows,
    prepareRow
  } = useTable({
    columns,
    data
  });

  return (
    <table {...getTableProps()}>
      <thead>
        {headerGroups.map(headerGroup => (
          <tr {...headerGroup.getHeaderGroupProps()}>
            {headerGroup.headers.map(column => (
              <th {...column.getHeaderProps()}>{column.render("Header")}</th>
            ))}
          </tr>
        ))}
      </thead>
      <tbody {...getTableBodyProps()}>
        {rows.map((row, i) => {
          prepareRow(row);
          return (
            <tr {...row.getRowProps()}>
              {row.cells.map(cell => {
                return <td {...cell.getCellProps()}>{cell.render("Cell")}</td>;
              })}
            </tr>
          );
        })}
      </tbody>
      <tfoot>
        {footerGroups.map(group => (
          <tr {...group.getFooterGroupProps()}>
            {group.headers.map(column => (
              <td {...column.getFooterProps()}>{column.render("Footer")}</td>
            ))}
          </tr>
        ))}
      </tfoot>
    </table>
  );
};

export default function App() {
  return (
    <div className="App">
      <Table columns={columns} data={data} />
    </div>
  );
}
```

In the code above, we added the `Footer` property to the `columns` array as follows:

```jsx
const columns = [
  {
    Header: "Name",
    Footer: "Name",
    columns: [
      {
        Header: "First Name",
        accessor: "firstName"
      },
      {
        Header: "Last Name",
        accessor: "lastName"
      }
    ]
  },
  {
    Header: "Other Info",
    Footer: "Other Info",
    columns: [
      {
        Header: "Age",
        accessor: "age",
        Footer: info => {
          const total = React.useMemo(
            () => info.rows.reduce((sum, row) => row.values.age + sum, 0),
            [info.rows]
          );

          return <>Average Age: {total / info.rows.length}</>;
        }
      }
    ]
  }
];
```

We added the `Footer` property to the top-level of each object.

Also, we add a function for the `Footer` property in the object for the Age column.

The `Footer` property in the object for the Age column is:

```jsx
info => {
  const total = React.useMemo(
    () => info.rows.reduce((sum, row) => row.values.age + sum, 0),
    [info.rows]
  );

  return <>Average Age: {total / info.rows.length}</>;
}
```

It takes the `info` object, which has all the table data. Then we summed by all the `age` property values for each entry and divided it by `info.row.length` to return the average age. This is displayed at the bottom of the table below the **Age** column.

The average will change as the row changes since we have `[info.rows]`, which watches the rows for changing values and recomputes the value when the rows change.

## Sorting

We can add sorting to a table by calling a few functions. We have to pass in the `useSortBy` Hook as the second argument of the `useTable` Hook to get sorting capability in our table.

Then, in our JSX code, we have to pass in `column.getSortByToggleProps()` to `column.getHeaderProps` to get the sorting order of the columns in the rendered column.

We can check the order in which a column is sorted by using the `column.isSorted` and `column.isSortedDesc` to check if a column is sorted by ascending or descending order, respectively.

Also, we can add a `sortType` property to the column array entries so we can specify the sort type. For instance, we can write the following code to add basic sorting to our table:

```jsx
import React from "react";
import { useTable, useSortBy } from "react-table";

const data = [
  { firstName: "jane", lastName: "doe" },
  { firstName: "john", lastName: "smith" }
];

const columns = [
  {
    Header: "Name",
    columns: [
      {
        Header: "First Name",
        accessor: "firstName",
        sortType: "basic"
      },
      {
        Header: "Last Name",
        accessor: "lastName",
        sortType: "basic"
      }
    ]
  }
];

const Table = ({ columns, data }) => {
  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    rows,
    prepareRow
  } = useTable(
    {
      columns,
      data
    },
    useSortBy
  );

  return (
    <table {...getTableProps()}>
      <thead>
        {headerGroups.map(headerGroup => (
          <tr {...headerGroup.getHeaderGroupProps()}>
            {headerGroup.headers.map(column => (
              <th {...column.getHeaderProps(column.getSortByToggleProps())}>
                {column.render("Header")}
                <span>
                  {column.isSorted ? (column.isSortedDesc ? " 🔽" : " 🔼") : ""}
                </span>
              </th>
            ))}
          </tr>
        ))}
      </thead>
      <tbody {...getTableBodyProps()}>
        {rows.map((row, i) => {
          prepareRow(row);
          return (
            <tr {...row.getRowProps()}>
              {row.cells.map(cell => {
                return <td {...cell.getCellProps()}>{cell.render("Cell")}</td>;
              })}
            </tr>
          );
        })}
      </tbody>
    </table>
  );
};

export default function App() {
  return (
    <div className="App">
      <Table columns={columns} data={data} />
    </div>
  );
}
```

In the code above, we specified that `sortType` is `'basic'` so that words are sorted alphabetically and numbers are sorted numerically.

Then we rendered the `thead` by writing:

```jsx
<thead>
  {headerGroups.map(headerGroup => (
    <tr {...headerGroup.getHeaderGroupProps()}>
      {headerGroup.headers.map(column => (
        <th {...column.getHeaderProps(column.getSortByToggleProps())}>
          {column.render("Header")}
          <span>
            {column.isSorted ? (column.isSortedDesc ? " 🔽" : " 🔼") : ""}
          </span>
        </th>
      ))}
    </tr>
  ))}
</thead>
```

This adds icons for indicating the sort order of each column and getting the order by which the column is sorted.

After writing that code, we’ll see a sort button to the right of our column headings that we can click on to sort the columns.

## Filtering

Filtering is more complex than creating a simple table or sorting. We have to create a component with an input control we can use to filter our items. The input component will take the functions that are returned from the `useTable` as props and call them in the inputs.

For instance, we can write the following code to do that:

```jsx
import React from "react";
import { useTable, useFilters, useGlobalFilter } from "react-table";

const data = [
  { firstName: "jane", lastName: "doe", age: 20 },
  { firstName: "john", lastName: "smith", age: 21 }
];

const columns = [
  {
    Header: "Name",
    columns: [
      {
        Header: "First Name",
        accessor: "firstName",
        filter: "text"
      },
      {
        Header: "Last Name",
        accessor: "lastName",
        filter: "text"
      }
    ]
  },
  {
    Header: "Other Info",
    columns: [
      {
        Header: "Age",
        accessor: "age",
        filter: "text"
      }
    ]
  }
];

const DefaultColumnFilter = ({
  column: { filterValue, preFilteredRows, setFilter }
}) => {
  const count = preFilteredRows.length;

  return (
    <input
      value={filterValue || ""}
      onChange={e => {
        setFilter(e.target.value || undefined);
      }}
      placeholder={`Search ${count} records...`}
    />
  );
};

const GlobalFilter = ({
  preGlobalFilteredRows,
  globalFilter,
  setGlobalFilter
}) => {
  const count = preGlobalFilteredRows && preGlobalFilteredRows.length;

  return (
    <span>
      Search:{" "}
      <input
        value={globalFilter || ""}
        onChange={e => {
          setGlobalFilter(e.target.value || undefined); // Set undefined to remove the filter entirely
        }}
        placeholder={`${count} records...`}
        style={{
          border: "0"
        }}
      />
    </span>
  );
};

const Table = ({ columns, data }) => {
  const filterTypes = React.useMemo(
    () => ({
      text: (rows, id, filterValue) => {
        return rows.filter(row => {
          const rowValue = row.values[id];
          return rowValue !== undefined
            ? String(rowValue)
                .toLowerCase()
                .startsWith(String(filterValue).toLowerCase())
            : true;
        });
      }
    }),
    []
  );

  const defaultColumn = React.useMemo(
    () => ({
      Filter: DefaultColumnFilter
    }),
    []
  );

  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    rows,
    prepareRow,
    state,
    visibleColumns,
    preGlobalFilteredRows,
    setGlobalFilter
  } = useTable(
    {
      columns,
      data,
      defaultColumn,
      filterTypes
    },
    useFilters,
    useGlobalFilter
  );

  return (
    <table {...getTableProps()}>
      <thead>
        {headerGroups.map(headerGroup => (
          <tr {...headerGroup.getHeaderGroupProps()}>
            {headerGroup.headers.map(column => (
              <th {...column.getHeaderProps()}>
                {column.render("Header")}
                <div>{column.canFilter ? column.render("Filter") : null}</div>
              </th>
            ))}
          </tr>
        ))}
        <tr>
          <th
            colSpan={visibleColumns.length}
            style={{
              textAlign: "left"
            }}
          >
            <GlobalFilter
              preGlobalFilteredRows={preGlobalFilteredRows}
              globalFilter={state.globalFilter}
              setGlobalFilter={setGlobalFilter}
            />
          </th>
        </tr>
      </thead>
      <tbody {...getTableBodyProps()}>
        {rows.map((row, i) => {
          prepareRow(row);
          return (
            <tr {...row.getRowProps()}>
              {row.cells.map(cell => {
                return <td {...cell.getCellProps()}>{cell.render("Cell")}</td>;
              })}
            </tr>
          );
        })}
      </tbody>
    </table>
  );
};

export default function App() {
  return (
    <div className="App">
      <Table columns={columns} data={data} />
    </div>
  );
}
```

In the code above, we added the `GlobalFilter` component as follows:

```jsx
const GlobalFilter = ({
  preGlobalFilteredRows,
  globalFilter,
  setGlobalFilter
}) => {
  const count = preGlobalFilteredRows && preGlobalFilteredRows.length;

  return (
    <span>
      Search:{" "}
      <input
        value={globalFilter || ""}
        onChange={e => {
          setGlobalFilter(e.target.value || undefined);
        }}
        placeholder={`${count} records...`}
        style={{
          border: "0"
        }}
      />
    </span>
  );
};
```

This is used to search all the columns present in the data by calling the `setGlobalFilter` function that’s passed in as props. The `preGlobalFilteredRows` is an array in which we can count the number of rows that we’re searching for.

Then, in the `Table` component, we added the following code:

```jsx
const filterTypes = React.useMemo(
  () => ({
    text: (rows, id, filterValue) => {
      return rows.filter(row => {
        const rowValue = row.values[id];
        return rowValue !== undefined
          ? String(rowValue)
              .toLowerCase()
              .startsWith(String(filterValue).toLowerCase())
          : true;
      });
    }
  }),
  []
);

const defaultColumn = React.useMemo(
  () => ({
    Filter: DefaultColumnFilter
  }),
  []
);

const {
  getTableProps,
  getTableBodyProps,
  headerGroups,
  rows,
  prepareRow,
  state,
  visibleColumns,
  preGlobalFilteredRows,
  setGlobalFilter
} = useTable(
  {
    columns,
    data,
    defaultColumn,
    filterTypes
  },
  useFilters,
  useGlobalFilter
);
```

The `defaultColumn` has a cached object, which has the `DefaultColumnFilter` set as follows:

```jsx
const DefaultColumnFilter = ({
  column: { filterValue, preFilteredRows, setFilter }
}) => {
  const count = preFilteredRows.length;

  return (
    <input
      value={filterValue || ""}
      onChange={e => {
        setFilter(e.target.value || undefined);
      }}
      placeholder={`Search ${count} records...`}
    />
  );
};
```

The `defaultColumn` caches the input component that’s used to search individual columns. We also have the `filterTypes` constant, which has the cached value of the filter we used to search our table.

We have an object with the `text` method, which is used to search the entries that we’re looking for as we type. In the method, we called `filter` on `rows` to return the items that start with the given search string, which is stored in `filterValue`.

We also used more of the returned properties from the `useTable` Hook and passed in more arguments to the Hook, including the `useFilters` and `useGlobalFilter` Hooks to let us filter by column and globally, respectively.

Also, we added the `defaultColumn` and `filterTypes` objects to the object in the first argument to let us set the component we’ll use to do the filtering by default. `filterTypes` lets us set the value to the name of our function that we created for returning filtered data from our array of data.

In the end, we get two inputs to filter each column individually and one that can filter the items from all columns globally.

## Pagination

We can add pagination using the `usePagination` Hook, which is passed in as the argument for the `useTable` Hook.

The `useTable` Hook then returns a bunch of pagination-related variables that we used to track the pagination and navigate to different pages.

To make a simple table with pagination, we can write the following code:

```jsx
import React from "react";
import { useTable, usePagination } from "react-table";

const firstNames = ["jane", "john", "alex"];
const lastName = ["smith", "jones"];

const data = Array(100)
  .fill()
  .map(a => ({
    firstName: firstNames[Math.floor(Math.random() * firstNames.length)],
    lastName: lastName[Math.floor(Math.random() * lastName.length)],
    age: Math.ceil(75 * Math.random())
  }));

const columns = [
  {
    Header: "Name",
    columns: [
      {
        Header: "First Name",
        accessor: "firstName"
      },
      {
        Header: "Last Name",
        accessor: "lastName"
      }
    ]
  },
  {
    Header: "Other Info",
    columns: [
      {
        Header: "Age",
        accessor: "age"
      }
    ]
  }
];

const Table = ({ columns, data }) => {
  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    prepareRow,
    page,
    canPreviousPage,
    canNextPage,
    pageOptions,
    pageCount,
    gotoPage,
    nextPage,
    previousPage,
    setPageSize,
    state: { pageIndex, pageSize }
  } = useTable(
    {
      columns,
      data,
      initialState: { pageIndex: 0 }
    },
    usePagination
  );

  return (
    <>
      <table {...getTableProps()}>
        <thead>
          {headerGroups.map(headerGroup => (
            <tr {...headerGroup.getHeaderGroupProps()}>
              {headerGroup.headers.map(column => (
                <th {...column.getHeaderProps()}>{column.render("Header")}</th>
              ))}
            </tr>
          ))}
        </thead>
        <tbody {...getTableBodyProps()}>
          {page.map((row, i) => {
            prepareRow(row);
            return (
              <tr {...row.getRowProps()}>
                {row.cells.map(cell => {
                  return (
                    <td {...cell.getCellProps()}>{cell.render("Cell")}</td>
                  );
                })}
              </tr>
            );
          })}
        </tbody>
      </table>
      <div>
        <button onClick={() => gotoPage(0)} disabled={!canPreviousPage}>
          {"<<"}
        </button>{" "}
        <button onClick={() => previousPage()} disabled={!canPreviousPage}>
          {"<"}
        </button>{" "}
        <button onClick={() => nextPage()} disabled={!canNextPage}>
          {">"}
        </button>{" "}
        <button onClick={() => gotoPage(pageCount - 1)} disabled={!canNextPage}>
          {">>"}
        </button>{" "}
        <span>
          Page{" "}
          <strong>
            {pageIndex + 1} of {pageOptions.length}
          </strong>{" "}
        </span>
        <span>
          | Go to page:{" "}
          <input
            type="number"
            defaultValue={pageIndex + 1}
            onChange={e => {
              const page = e.target.value ? Number(e.target.value) - 1 : 0;
              gotoPage(page);
            }}
            style={{ width: "100px" }}
          />
        </span>{" "}
        <select
          value={pageSize}
          onChange={e => {
            setPageSize(Number(e.target.value));
          }}
        >
          {[10, 20, 30, 40, 50].map(pageSize => (
            <option key={pageSize} value={pageSize}>
              Show {pageSize}
            </option>
          ))}
        </select>
      </div>
    </>
  );
};

export default function App() {
  return (
    <div className="App">
      <Table columns={columns} data={data} />
    </div>
  );
}
```

In the code above, we generated 100 array entries with random names and pages of people. The headers are the same as the simple table example above.

In the `Table` component, we have:

```jsx
const {
  getTableProps,
  getTableBodyProps,
  headerGroups,
  prepareRow,
  page,
  canPreviousPage,
  canNextPage,
  pageOptions,
  pageCount,
  gotoPage,
  nextPage,
  previousPage,
  setPageSize,
  state: { pageIndex, pageSize }
} = useTable(
  {
    columns,
    data,
    initialState: { pageIndex: 0 }
  },
  usePagination
);
```

With this, we can get various pieces of data we need for pagination, like `pageSize` to change the number of items displayed on each page.

`canPreviousPage` and `canNextPage` tell us whether we can move to the previous or next page respectively. `pageCount` has the total page count, and `gotoPage` is a function that lets us skip to the given page number. `previousPage` and `nextPage` are also functions that let us navigate to the given page.

They’re used in the following `div` to navigate between pages:

```jsx
<div>
  <button onClick={() => gotoPage(0)} disabled={!canPreviousPage}>
    {"<<"}
  </button>{" "}
  <button onClick={() => previousPage()} disabled={!canPreviousPage}>
    {"<"}
  </button>{" "}
  <button onClick={() => nextPage()} disabled={!canNextPage}>
    {">"}
  </button>{" "}
  <button onClick={() => gotoPage(pageCount - 1)} disabled={!canNextPage}>
    {">>"}
  </button>{" "}
  <span>
    Page{" "}
    <strong>
      {pageIndex + 1} of {pageOptions.length}
    </strong>{" "}
  </span>
  <span>
    | Go to page:{" "}
    <input
      type="number"
      defaultValue={pageIndex + 1}
      onChange={e => {
        const page = e.target.value ? Number(e.target.value) - 1 : 0;
        gotoPage(page);
      }}
      style={{ width: "100px" }}
    />
  </span>{" "}
  <select
    value={pageSize}
    onChange={e => {
      setPageSize(Number(e.target.value));
    }}
  >
    {[10, 20, 30, 40, 50].map(pageSize => (
      <option key={pageSize} value={pageSize}>
        Show {pageSize}
      </option>
    ))}
  </select>
</div>
```

Then we get a table with the same columns as in the example above, but with pagination buttons added. We can also use the dropdown to change the size of each page.

## Integration with Material UI

The `react-table` package integrates with Material UI to let us create a table that follows the Material Design specification.

To install Material UI, we run:

```jsx
npm install @material-ui/core
```

Then we can use Material UI’s table components with `react-table` to create the table as follows:

```jsx
import React from "react";
import { useTable } from "react-table";
import MaUTable from "@material-ui/core/Table";
import TableBody from "@material-ui/core/TableBody";
import TableCell from "@material-ui/core/TableCell";
import TableHead from "@material-ui/core/TableHead";
import TableRow from "@material-ui/core/TableRow";

const data = [
  { firstName: "jane", lastName: "doe", age: 20 },
  { firstName: "john", lastName: "smith", age: 21 }
];

const columns = [
  {
    Header: "Name",
    columns: [
      {
        Header: "First Name",
        accessor: "firstName"
      },
      {
        Header: "Last Name",
        accessor: "lastName"
      }
    ]
  },
  {
    Header: "Other Info",
    columns: [
      {
        Header: "Age",
        accessor: "age"
      }
    ]
  }
];

const Table = ({ columns, data }) => {
  const { getTableProps, headerGroups, rows, prepareRow } = useTable({
    columns,
    data
  });

  return (
    <MaUTable {...getTableProps()}>
      <TableHead>
        {headerGroups.map(headerGroup => (
          <TableRow {...headerGroup.getHeaderGroupProps()}>
            {headerGroup.headers.map(column => (
              <TableCell {...column.getHeaderProps()}>
                {column.render("Header")}
              </TableCell>
            ))}
          </TableRow>
        ))}
      </TableHead>
      <TableBody>
        {rows.map((row, i) => {
          prepareRow(row);
          return (
            <TableRow {...row.getRowProps()}>
              {row.cells.map(cell => {
                return (
                  <TableCell {...cell.getCellProps()}>
                    {cell.render("Cell")}
                  </TableCell>
                );
              })}
            </TableRow>
          );
        })}
      </TableBody>
    </MaUTable>
  );
};

export default function App() {
  return (
    <div className="App">
      <Table columns={columns} data={data} />
    </div>
  );
}
```

In the code above, we used the Material UI components to render the table, but the data is populated by `react-table`. We called the same methods we used in the simple table example to populate the rows and columns with data.

Therefore, we get the same data and columns as the simple table example, but it adheres to Material Design instead of having no styling.

## Conclusion

As we can see, react-table is capable of creating tables with lots of capabilities without having to create everything from scratch ourselves.

It provides us with a Hooks-based API to create tables, which is important since some devs would like to switch to using function components with Hooks now.

There are many more examples to showcase what react-table can do on [its official GitHub repo](https://github.com/tannerlinsley/react-table). Some examples are simplified from the examples on their official website.

* * * 
## Full visibility into production React apps 

Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, [try LogRocket.](https://www2.logrocket.com/react-performance-monitoring)

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/eq752g8qhbffxt3hp9t4.png)

[LogRocket](https://www2.logrocket.com/react-performance-monitoring) is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — [start monitoring for free.](https://www2.logrocket.com/react-performance-monitoring)
* * *

The post [Building and styling tables with react-table v7](https://blog.logrocket.com/building-styling-tables-react-table-v7/) appeared first on [LogRocket Blog](https://blog.logrocket.com).