React Data Table ⚛️ with Material UI and a Spark of Joy 😛
Tags: javascript, react, materialui, tutorial
Published: 2020-10-15 17:02:55

When you want to visualize large amounts of uniform data, charts don't work well because they effectively hide information about individual data items. **However, it's the case when data tables come in handy!** 😇 

In this tutorial, we'll learn how to display massive loads of data in a data table that is built from scratch in React. We'll explore how to fetch data from a database via an API and visualize it in a data table with essential features like filtering, sorting, etc.

We'll use [Material UI](https://material-ui.com) because it's the most popular UI framework for React. Created with inspiration from Google's [Material Design](https://material.io/design/), it provides a lot of components that we can use to get a nice looking user interface.

# How to Build a Data Table 🤔

Here's our plan for today!

- [Prepare the data](#toc-data) in the database — lots of data!
- [Launch an API](#toc-api) to work with that data, fast and easy
- [Create an application](#toc-app) with React and Material UI
- [Build a basic data table](#toc-build)
- [Extend the data table](#toc-extend) with various features, step by step

Sounds nice? Let's go!

Before you dive into hacking, see a screenshot of the data table we're going to build. Also, check out the [full source code](https://github.com/cube-js/cube.js/tree/master/examples/react-data-table) available on GitHub.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/mf0pspqdy8s86e6c8k90.png)

# Prepare the data in the database 💾 <a name="toc-data"></a>

We’ll use, I guess, one of the most popular SQL data stores — [PostgreSQL](https://www.postgresql.org/) database. Please make sure you have PostgreSQL installed. (Otherwise, it may stop being so popular one day 😛.)

Now we can download and import a carefully prepared sample e-commerce dataset for PostgreSQL. The data set is related an imaginary e-commerce company that wants to track its orders and their statuses:

```bash
$ curl <http://cube.dev/downloads/ecom-dump.sql> > ecom-dump.sql
$ createdb ecom
$ psql --dbname ecom -f ecom-dump.sql
```

So, the database is ready! Let's proceed to...

# Launch an API to work with data 🚀 <a name="toc-api"></a>

We're going to use [Cube.js](https://cube.dev?utm_source=dev-to&utm_medium=post&utm_campaign=react-data-table) for our API. Cube.js is an open-source analytical API platform that helps to create APIs for SQL data stores and build analytical apps. It removes all the hustle of building the API layer, generating SQL, and querying the database. It also provides many production-grade features like multi-level caching for optimal performance, multi-tenancy, security, and more.

So, let's launch an API on top of our database with Cube.js in a few simple steps.

**First, we need to install the Cube.js command-line utility (CLI).** For convenience, let's install it globally on our machine.

`$ npm install -g cubejs-cli`

Then, with the CLI installed, we can create a basic backend by running a single command. Cube.js supports [all popular databases](https://cube.dev/docs/getting-started?utm_source=dev-to&utm_medium=post&utm_campaign=react-data-table#2-connect-to-your-database), so we can pre-configure the backend to work with PostgreSQL:

`$ cubejs create <project name> -d <database type>`

**To create the backend, we run this command:**

`$ cubejs create react-data-table -d postgres`

Now we need to [connect](https://cube.dev/docs/connecting-to-the-database?utm_source=dev-to&utm_medium=post&utm_campaign=react-data-table#configuring-connection-for-cube-js-cli-created-apps) it to the database. In order to do that, we provide a few options via the `.env` file in the root of the Cube.js project folder (`react-data-table`):

```bash
CUBEJS_DB_NAME=ecom
CUBEJS_DB_TYPE=postgres
CUBEJS_API_SECRET=secret
```

![](https://d2cq47x6codx9u.cloudfront.net/images/start.gif)

Now we can run the backend!

**In development mode, the backend will also run the Cube.js Playground.** That's really cool. 🤘 Cube.js Playground a time-saving web application that helps to create a data schema, test out the queries, and generate a React dashboard boilerplate. Run the following command in the Cube.js project folder:

`$ node index.js`

Next, open [http://localhost:4000](http://localhost:4000/) in your browser.

![](https://d2cq47x6codx9u.cloudfront.net/images/demo.gif)

**We'll use the Cube.js Playground to create a data schema.** It's essentially a JavaScript code that declaratively describes the data, defines analytical entities like measures and dimensions, and maps them to SQL queries. Here is an example of the schema which can be used to describe products’ data.

```jsx
cube(`Products`, {
  sql: `SELECT * FROM public.products`,

  measures: {
    count: {
      type: `count`
    }
  },

  dimensions: {
    name: {
      sql: `name`,
      type: `string`
    },

    id: {
      sql: `id`,
      type: `number`,
      primaryKey: true,
      shown: true
    },

    description: {
      sql: `description`,
      type: `string`
    },

    createdAt: {
      sql: `created_at`,
      type: `time`
    }
  }
});
```

Cube.js can generate a simple data schema based on the database’s tables. If you already have a non-trivial set of tables in your database, consider using the data schema generation because it can save a lot of time.

So, let's navigate to the Schema tab of Cube.js Playground, select `public` group in the tree view, select the `line_items`, `orders`, `products`, and `users` tables, and click “Generate Schema.” As the result, we'll have 4 generated files in the `schema` folder — exactly one schema file per table.

![](https://d2cq47x6codx9u.cloudfront.net/images/image-37.png)

**Once the schema is generated, we can query the data via the Cube.js Playground.** To do so, navigate to the “Build” tab and select some measures and dimensions from the schema. Looks like magic, isn't it?

The "Build" tab is a place where you can build sample charts using different visualization libraries and inspect every aspect of how that chart was created, starting from the generated SQL all the way up to the JavaScript code to render the chart. You can also inspect the Cube.js query encoded with JSON which is sent to Cube.js backend.

![](https://d2cq47x6codx9u.cloudfront.net/images/image-05.png)

Okay, we're all set. The API is ready, now let's...

# Create an application with React ⚛️ <a name="toc-app"></a>

Big news! 😛

**The Cube.js Playground can generate a template for any chosen frontend framework and charting library for you.** To create a template for our application, navigate to the "Dashboard App" and use these options:

- Framework: `React`
- Main Template: `React Material UI Static`
- Charting Library: `Chart.js`

![](https://d2cq47x6codx9u.cloudfront.net/images/image-0.gif)

Congratulations! Now we have the `dashboard-app` folder in our project. This folder contains all the frontend code that we're going to extend.

Before we proceed, let's make the most crucial change — show in the title that we're building a data table. 😝 

To do so, chnage a few lines in the `public/index.html` file of the `dashboard-app` as follows:

```diff
// ...

-    <title>React App</title>
+    <title>React Data Table</title>
+    <style>
+      body {
+          background-color: #eeeeee;
+          margin: 0;
+      }
+    </style>	
// ...
```

Also, let's install a few dependencies of `dashboard-app` that will make our task of building a data table easier:

```bash
$ npm install --save react-perfect-scrollbar @material-ui/pickers
```

So, now we're ready to...

# Build a basic data table 🏗 <a name="toc-build"></a>

It's great to have lots of data in the table, right? So, let's fetch them via the API.

To do so, we're going to define a number of new metrics: amount of items in an order (its size), an order's price, and a user's full name. With Cube.js, it's super-easy:

First, let's add the full name in the "Users" schema in the `schema/Users.js` file. To create the full name, we concatenate first name and last name using the SQL function `CONCAT`:

```diff
cube(`Users`, {
  sql: `SELECT * FROM public.users`,

// ...

  dimensions: {    
		
// ...

    id: {
+      shown: true,
      sql: `id`,
      type: `number`,
      primaryKey: true
    },

    firstName: {
      sql: `first_name`,
      type: `string`
    },

    lastName: {
      sql: `last_name`,
      type: `string`
    },

+    fullName: {
+      sql: `CONCAT(${firstName}, ' ', ${lastName})`,
+      type: `string`
+    },

// ...
```

Then, let's add other measures to the "Orders" schema in the `schema/Orders.js` file.

For these measures, we're going to use the [subquery](https://cube.dev/docs/subquery?utm_source=dev-to&utm_medium=post&utm_campaign=react-data-table#top) feature of Cube.js. You can use subquery dimensions to reference measures from other cubes inside a dimension. Here's how to define such dimensions:

```diff
cube(`Orders`, {
  sql: `SELECT * FROM public.orders`,

  dimensions: {

// ...

    id: {
+      shown: true,
      sql: `id`,
      type: `number`,
      primaryKey: true
    },

    createdAt: {
      sql: `created_at`,
      type: `time`
    },

+    size: {
+      sql: `${LineItems.count}`,
+      subQuery: true,
+      type: 'number'
+    },
+
+    price: {
+      sql: `${LineItems.price}`,
+      subQuery: true,
+      type: 'number'
+    },

    completedAt: {
      sql: `completed_at`,
      type: `time`
    }
  }
});
```

We're almost there! So, to display the data table, let's replace the `src/pages/DashboardPage.js` file with the following contents:

```jsx
import React from "react";
import { makeStyles } from "@material-ui/styles";

import Table from "../components/Table.js";

const useStyles = makeStyles(theme => ({
  root: { padding: 15 },
  content: { marginTop: 15 },
}));

const Dashboard = () => {
  const classes = useStyles();

  const query = {
    timeDimensions: [
      {
        dimension: 'Orders.createdAt',
        granularity: 'day'
      }
    ],
    dimensions: [
      'Users.id',
      'Orders.id',
      'Orders.size',
      'Users.fullName',
      'Users.city',
      'Orders.price',
      'Orders.status',
      'Orders.createdAt',
    ]
  };

  return (
    <div className={classes.root}>
      <div className={classes.content}>
        <Table query={query}/>
      </div>
    </div>
  );
};

export default Dashboard;
```

Note that now this file contains a Cube.js query which is quite self-explanatory: we're just asking the API to return a number of dimensions, and it does the thing.

The changes to the `Dashboard` are minimal, however, all the magic of rendering a data table happens inside the `<Table />` component, and changes to the query result are reflected in the table.

Let's create this `<Table />` component in the `src/components/Table.js` file with the following contents:

```jsx
import React, { useState } from "react";
import clsx from "clsx";
import PropTypes from "prop-types";
import moment from "moment";
import PerfectScrollbar from "react-perfect-scrollbar";
import { makeStyles } from "@material-ui/styles";
import Typography from "@material-ui/core/Typography";
import { useCubeQuery } from "@cubejs-client/react";
import CircularProgress from "@material-ui/core/CircularProgress";
import {
  Card,
  CardActions,
  CardContent,
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableRow,
  TablePagination
} from "@material-ui/core";

const useStyles = makeStyles(theme => ({
  root: {
    padding: 0
  },
  content: {
    padding: 0
  },
  inner: {
    minWidth: 1050
  },
  nameContainer: {
    display: "flex",
    alignItems: "baseline"
  },
  status: {
    marginRight: 15
  },
  actions: {
    justifyContent: "flex-end"
  },
}));

const TableComponent = props => {

  const { className, query, cubejsApi, ...rest } = props;

  const classes = useStyles();

  const [rowsPerPage, setRowsPerPage] = useState(10);
  const [page, setPage] = useState(0);

  const tableHeaders = [
    { text: "Full Name", value: "Users.fullName" },
    { text: "User city", value: "Users.city" },
    { text: "Order price", value: "Orders.price" },
    { text: "Status", value: "Orders.status" },
    { text: "Created at", value: "Orders.createdAt" }
  ];
  const { resultSet, error, isLoading } = useCubeQuery(query, { cubejsApi });
  if (isLoading) {
    return <div style={{display: 'flex', alignItems: 'center', justifyContent: 'center'}}><CircularProgress color="secondary" /></div>;
  }
  if (error) {
    return <pre>{error.toString()}</pre>;
  }
  if (resultSet) {
    let orders = resultSet.tablePivot();

    const handlePageChange = (event, page) => {
      setPage(page);
    };
    const handleRowsPerPageChange = event => {
      setRowsPerPage(event.target.value);
    };

    return (
      <Card
        {...rest}
        padding={"0"}
        className={clsx(classes.root, className)}
      >
        <CardContent className={classes.content}>
          <PerfectScrollbar>
            <div className={classes.inner}>
              <Table>
                <TableHead className={classes.head}>
                  <TableRow>
                    {tableHeaders.map((item) => (
                      <TableCell key={item.value + Math.random()} 
																 className={classes.hoverable}           
                      >
                        <span>{item.text}</span>
              
                      </TableCell>
                    ))}
                  </TableRow>
                </TableHead>
                <TableBody>
                  {orders.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map(obj => (
                    <TableRow
                      className={classes.tableRow}
                      hover
                      key={obj["Orders.id"]}
                    >
                      <TableCell>
                        {obj["Orders.id"]}
                      </TableCell>
                      <TableCell>
                        {obj["Orders.size"]}
                      </TableCell>
                      <TableCell>
                        {obj["Users.fullName"]}
                      </TableCell>
                      <TableCell>
                        {obj["Users.city"]}
                      </TableCell>
                      <TableCell>
                        {"$ " + obj["Orders.price"]}
                      </TableCell>
                      <TableCell>
                        {obj["Orders.status"]}
                      </TableCell>
                      <TableCell>
                        {moment(obj["Orders.createdAt"]).format("DD/MM/YYYY")}
                      </TableCell>
                    </TableRow>
                  ))}
                </TableBody>
              </Table>
            </div>
          </PerfectScrollbar>
        </CardContent>
        <CardActions className={classes.actions}>
          <TablePagination
            component="div"
            count={orders.length}
            onChangePage={handlePageChange}
            onChangeRowsPerPage={handleRowsPerPageChange}
            page={page}
            rowsPerPage={rowsPerPage}
            rowsPerPageOptions={[5, 10, 25, 50, 100]}
          />
        </CardActions>
      </Card>
    );
  } else {
    return null
  }
};

TableComponent.propTypes = {
  className: PropTypes.string,
  query: PropTypes.object.isRequired
};

export default TableComponent;
```

Finally! Here's the data table we were waiting for:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/38n20o6xpfqgpkmk0h5i.png)

Looks great, yeah?

Please note that it actually is not so basic! 😜 You have a built-in pagination that allows to display and navigate vast amounts of data.

However, it looks grey-ish and gloomy. So, let's add color and extend the table with...

## Custom Cell Format <a name="toc-extend"></a>

The table contains orders' statuses which are at displayed as text at this point. Let's replace them with a custom component!

The idea is to visualize an order's status with a colorful dot. For that, we'll create a custom `<StatusBullet />` component. Let's create this component in the `src/components/StatusBullet.js` file with the following contents:

```jsx
import React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import { makeStyles } from '@material-ui/styles';

const useStyles = makeStyles(theme => ({
  root: {
    display: 'inline-block',
    borderRadius: '50%',
    flexGrow: 0,
    flexShrink: 0
  },
  sm: {
    height: 15,
    width: 15
  },
  md: {
    height: 15,
    width: 15
  },
  lg: {
    height: 15,
    width: 15
  },
  neutral: { backgroundColor: '#fff' },
  primary: { backgroundColor: '#ccc' },
  info: { backgroundColor: '#3cc' },
  warning: { backgroundColor: '#cc3' },
  danger: { backgroundColor: '#c33' },
  success: { backgroundColor: '#3c3' }
}));

const StatusBullet = props => {
  const { className, size, color, ...rest } = props;

  const classes = useStyles();

  return (
    <span
      {...rest}
      className={clsx(
        {
          [classes.root]: true,
          [classes[size]]: size,
          [classes[color]]: color
        },
        className
      )}
    />
  );
};

StatusBullet.propTypes = {
  className: PropTypes.string,
  color: PropTypes.oneOf([
    'neutral',
    'primary',
    'info',
    'success',
    'warning',
    'danger'
  ]),
  size: PropTypes.oneOf(['sm', 'md', 'lg'])
};

StatusBullet.defaultProps = {
  size: 'md',
  color: 'default'
};

export default StatusBullet;
```

To make it work, we'll need to apply some minimal changes to the data table. Let's modify the `src/components/Table.js` as follows:

```diff
// ...

} from "@material-ui/core";

import StatusBullet from "./StatusBullet";

const statusColors = {
  completed: "success",
  processing: "info",
  shipped: "danger"
};

const useStyles = makeStyles(theme => ({

// ...

<TableCell>
+  <StatusBullet
+    className={classes.status}
+    color={statusColors[obj["Orders.status"]]}
+    size="sm"
+  />
  {obj["Orders.status"]}
</TableCell>

// ...
```

Nice! 🎉 Now we have a table which displays information about all orders and has some colorful touch: 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ensiabp292kndh4d4726.png)

## Filtering the Data

However, its hard to explore these orders using only the controls provided. To fix this, we'll add a comprehensive toolbar with filters and make our table interactive.

First, let's add a few dependencies. Run the command in the `dashboard-app` folder:

```jsx
npm install --save @date-io/date-fns@1.x date-fns @date-io/moment@1.x moment
```

Then, create the `<Toolbar />` component in the `src/components/Toolbar.js` file with the following contents:

```jsx
import "date-fns";
import React from "react";
import PropTypes from "prop-types";
import { makeStyles } from "@material-ui/styles";
import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import Tab from "@material-ui/core/Tab";
import Tabs from "@material-ui/core/Tabs";
import withStyles from "@material-ui/core/styles/withStyles";

const AntTabs = withStyles({
  indicator: {},
})(Tabs);
const AntTab = withStyles((theme) => ({
  root: {
    textTransform: 'none',
    minWidth: 25,
    fontSize: 12,
    fontWeight: theme.typography.fontWeightRegular,
    marginRight: 0,
    opacity: 0.6,
    '&:hover': {
      opacity: 1,
    },
    '&$selected': {
      fontWeight: theme.typography.fontWeightMedium,
      outline: 'none',
    },
    '&:focus': {
      outline: 'none',
    },
  },
  selected: {},
}))((props) => <Tab disableRipple {...props} />);
const useStyles = makeStyles(theme => ({
  root: {},
  row: {
    marginTop: 15
  },
  spacer: {
    flexGrow: 1
  },
  importButton: {
    marginRight: 15
  },
  exportButton: {
    marginRight: 15
  },
  searchInput: {
    marginRight: 15
  },
  formControl: {
    margin: 25,
    fullWidth: true,
    display: "flex",
    wrap: "nowrap"
  },
  date: {
    marginTop: 3
  },
  range: {
    marginTop: 13
  }
}));

const Toolbar = props => {
  const { className,
    statusFilter,
    setStatusFilter,
    tabs,
    ...rest } = props;
  const [tabValue, setTabValue] = React.useState(statusFilter);

  const classes = useStyles();

  const handleChangeTab = (e, value) => {
    setTabValue(value);
    setStatusFilter(value);
  };

  return (
    <div
      {...rest}
      className={className}
    >
      <Grid container spacing={4}>
        <Grid
          item
          lg={3}
          sm={6}
          xl={3}
          xs={12}
          m={2}
        >
          <div className={classes}>
            <AntTabs value={tabValue} onChange={(e,value) => {handleChangeTab(e,value)}} aria-label="ant example">
              {tabs.map((item) => (<AntTab key={item} label={item} />))}
            </AntTabs>
            <Typography className={classes.padding} />
          </div>
        </Grid>
      </Grid>
    </div>
  );
};

Toolbar.propTypes = {
  className: PropTypes.string
};

export default Toolbar;
```

Let's modify the `src/pages/DashboardPage` file:

```diff
import React from "react";
import { makeStyles } from "@material-ui/styles";

+ import Toolbar from "../components/Toolbar.js";
import Table from "../components/Table.js";

const useStyles = makeStyles(theme => ({
  root: {
    padding: 15
  },
  content: {
    marginTop: 15
  },
}));

const DashboardPage = () => {
  const classes = useStyles();
+  const tabs = ['All', 'Shipped', 'Processing', 'Completed'];
+  const [statusFilter, setStatusFilter] = React.useState(0);

  const query = {
    "timeDimensions": [
      {
        "dimension": "Orders.createdAt",
        "granularity": "day"
      }
    ],
    "dimensions": [
      "Users.id",
      "Orders.id",
      "Orders.size",
      "Users.fullName",
      "Users.city",
      "Orders.price",
      "Orders.status",
      "Orders.createdAt"
    ],
+    "filters": [
+      {
+        "dimension": "Orders.status",
+        "operator": tabs[statusFilter] !== 'All' ? "equals" : "set",
+        "values": [
+          `${tabs[statusFilter].toLowerCase()}`
+        ]
+      }
+    ]
  };

  return (
    <div className={classes.root}>
+      <Toolbar
+        statusFilter={statusFilter}
+        setStatusFilter={setStatusFilter}
+        tabs={tabs}
+      />
      <div className={classes.content}>
        <Table
          query={query}/>
      </div>
    </div>
  );
};

export default DashboardPage;
```

Perfect! 🎉 Now the data table has a filter which switches between different types of orders:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/l6wafc2i2ccm0glerczf.png)

However, orders have other parameters such as price and dates. Let's create filters for these parameters. To do so, modify the `src/components/Toolbar.js` file:

```diff
import "date-fns";
import React from "react";
import PropTypes from "prop-types";
import clsx from "clsx";
import { makeStyles } from "@material-ui/styles";
import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import Tab from "@material-ui/core/Tab";
import Tabs from "@material-ui/core/Tabs";
import withStyles from "@material-ui/core/styles/withStyles";
+ import DateFnsUtils from "@date-io/date-fns";
+ import {
+   MuiPickersUtilsProvider,
+   KeyboardDatePicker
+ } from "@material-ui/pickers";
+ import Slider from "@material-ui/core/Slider";

// ...

const Toolbar = props => {
  const { className,
+   startDate,
+   setStartDate,
+   finishDate,
+   setFinishDate,
+   priceFilter,
+   setPriceFilter,
    statusFilter,
    setStatusFilter,
    tabs,
    ...rest } = props;
  const [tabValue, setTabValue] = React.useState(statusFilter);
+ const [rangeValue, rangeSetValue] = React.useState(priceFilter);

  const classes = useStyles();

  const handleChangeTab = (e, value) => {
    setTabValue(value);
    setStatusFilter(value);
  };
+  const handleDateChange = (date) => {
+    setStartDate(date);
+  };
+  const handleDateChangeFinish = (date) => {
+    setFinishDate(date);
+  };
+ const handleChangeRange = (event, newValue) => {
+   rangeSetValue(newValue);
+ };
+ const setRangeFilter = (event, newValue) => {
+   setPriceFilter(newValue);
+ };

  return (
    <div
      {...rest}
      className={clsx(classes.root, className)}
    >
      <Grid container spacing={4}>
        <Grid
          item
          lg={3}
          sm={6}
          xl={3}
          xs={12}
          m={2}
        >
          <div className={classes}>
            <AntTabs value={tabValue} onChange={(e,value) => {handleChangeTab(e,value)}} aria-label="ant example">
              {tabs.map((item) => (<AntTab key={item} label={item} />))}
            </AntTabs>
            <Typography className={classes.padding} />
          </div>
        </Grid>
+        <Grid
+          className={classes.date}
+          item
+          lg={3}
+          sm={6}
+          xl={3}
+          xs={12}
+          m={2}
+        >
+          <MuiPickersUtilsProvider utils={DateFnsUtils}>
+            <Grid container justify="space-around">
+              <KeyboardDatePicker
+                id="date-picker-dialog"
+               label={<span style={{opacity: 0.6}}>Start Date</span>}
+                format="MM/dd/yyyy"
+                value={startDate}
+                onChange={handleDateChange}
+                KeyboardButtonProps={{
+                  "aria-label": "change date"
+                }}
+              />
+            </Grid>
+          </MuiPickersUtilsProvider>
+        </Grid>
+        <Grid
+          className={classes.date}
+          item
+          lg={3}
+          sm={6}
+          xl={3}
+          xs={12}
+          m={2}
+        >
+          <MuiPickersUtilsProvider utils={DateFnsUtils}>
+            <Grid container justify="space-around">
+              <KeyboardDatePicker
+                id="date-picker-dialog-finish"
+                label={<span style={{opacity: 0.6}}>Finish Date</span>}
+                format="MM/dd/yyyy"
+                value={finishDate}
+                onChange={handleDateChangeFinish}
+                KeyboardButtonProps={{
+                  "aria-label": "change date"
+                }}
+              />
+            </Grid>
+          </MuiPickersUtilsProvider>
+        </Grid>
+        <Grid
+          className={classes.range}
+          item
+          lg={3}
+          sm={6}
+          xl={3}
+          xs={12}
+          m={2}
+        >
+          <Typography id="range-slider">
+            Order price range
+          </Typography>
+          <Slider
+            value={rangeValue}
+            onChange={handleChangeRange}
+            onChangeCommitted={setRangeFilter}
+            aria-labelledby="range-slider"
+            valueLabelDisplay="auto"
+            min={0}
+            max={2000}
+          />
+        </Grid>
      </Grid>
    </div>
  );
};

Toolbar.propTypes = {
  className: PropTypes.string
};

export default Toolbar;
```

To make these filters work, we need to connect them to the parent component: add state, modify our query, and add new props to the `<Toolbar />` component. Also, we will add sorting to the data table. So, modify the `src/pages/DashboardPage.js` file like this:

```diff
// ...

const DashboardPage = () => {
  const classes = useStyles();
  const tabs = ['All', 'Shipped', 'Processing', 'Completed'];
  const [statusFilter, setStatusFilter] = React.useState(0);
+ const [startDate, setStartDate] = React.useState(new Date("2019-01-01T00:00:00"));
+ const [finishDate, setFinishDate] = React.useState(new Date("2022-01-01T00:00:00"));
+ const [priceFilter, setPriceFilter] = React.useState([0, 200]);
+ const [sorting, setSorting] = React.useState(['Orders.createdAt', 'desc']);

  const query = {
    timeDimensions: [
      {
        "dimension": "Orders.createdAt",
+	 "dateRange": [startDate, finishDate],
        "granularity": "day"
      }
    ],
+    order: {
+      [`${sorting[0]}`]: sorting[1]
+    },
    "dimensions": [
      "Users.id",
      "Orders.id",
      "Orders.size",
      "Users.fullName",
      "Users.city",
      "Orders.price",
      "Orders.status",
      "Orders.createdAt"
    ],
    "filters": [
      {
        "dimension": "Orders.status",
        "operator": tabs[statusFilter] !== 'All' ? "equals" : "set",
        "values": [
          `${tabs[statusFilter].toLowerCase()}`
        ]
      },
+     {
+        "dimension": "Orders.price",
+        "operator": "gt",
+        "values": [
+         `${priceFilter[0]}`
+       ]
+     },
+     {
+       "dimension": "Orders.price",
+       "operator": "lt",
+       "values": [
+         `${priceFilter[1]}`
+       ]
+     },
    ]
  };

  return (
    <div className={classes.root}>
      <Toolbar
+       startDate={startDate}
+       setStartDate={setStartDate}
+       finishDate={finishDate}
+       setFinishDate={setFinishDate}
+       priceFilter={priceFilter}
+       setPriceFilter={setPriceFilter}
        statusFilter={statusFilter}
        setStatusFilter={setStatusFilter}
        tabs={tabs}
      />
      <div className={classes.content}>
        <Table
+          sorting={sorting}
+          setSorting={setSorting}
          query={query}/>
      </div>
    </div>
  );
};

export default DataTablePage;
```

Fantastic! 🎉 We've added some useful filters. Indeed, you can add even more filters with custom logic. See the [documentation](https://cube.dev/docs/query-format?utm_source=dev-to&utm_medium=post&utm_campaign=react-data-table#filters-format) for the filter format options.

And there's one more thing! We've added sorting props to the toolbar, but we also need to pass them to the `<Table />` component. To fix this, let's modify the `src/components/Table.js` file:

```diff
// ...

+ import KeyboardArrowUpIcon from "@material-ui/icons/KeyboardArrowUp";
+ import KeyboardArrowDownIcon from "@material-ui/icons/KeyboardArrowDown";
import { useCubeQuery } from "@cubejs-client/react";
import CircularProgress from "@material-ui/core/CircularProgress";

// ...

const useStyles = makeStyles(theme => ({
  // ...
  actions: {
    justifyContent: "flex-end"
  },
+ tableRow: {
+   padding: '0 5px',
+   cursor: "pointer",
+   '.MuiTableRow-root.MuiTableRow-hover&:hover': {
+   }
+ },
+ hoverable: {
+   "&:hover": {
+     cursor: `pointer`
+   }
+ },
+ arrow: {
+   fontSize: 10,
+   position: "absolute"
+ }
}));

const statusColors = {
  completed: "success",
  processing: "info",
  shipped: "danger"
};

const TableComponent = props => {
-  const { className, query, cubejsApi, ...rest } = props;
+  const { className, sorting, setSorting, query, cubejsApi, ...rest } = props;

// ...

  if (resultSet) {

//...

+     const handleSetSorting = str => {
+       setSorting([str, sorting[1] === "desc" ? "asc" : "desc"]);
+     };

    return (
							// ...

                <TableHead className={classes.head}>
                  <TableRow>
                    {tableHeaders.map((item) => (
                      <TableCell key={item.value + Math.random()} className={classes.hoverable}
+                                 onClick={() => {
+                                 handleSetSorting(`${item.value}`);
+                                 }}
                      >
                        <span>{item.text}</span>
+                        <Typography
+                          className={classes.arrow}
+                          variant="body2"
+                          component="span"
+                        >
+                          {(sorting[0] === item.value) ? (sorting[1] === "desc" ? <KeyboardArrowUpIcon/> :
+                            <KeyboardArrowDownIcon/>) : null}
+                        </Typography>
                      </TableCell>
                    ))}
                  </TableRow>
                </TableHead>
						 // ...
```

Wonderful! 🎉 Now we have the data table that fully supports filtering and sorting: 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/mf0pspqdy8s86e6c8k90.png)

And that's all! 😇 Congratulations on completing this tutorial! 🎉

Also, check the [full source code](https://github.com/cube-js/cube.js/tree/master/examples/react-data-table) available on GitHub.

Now you should be able to create custom data tables powered by Cube.js with React and Material UI to display literally any amounts of data in your applications.

Feel free to explore [other examples](https://github.com/cube-js/cube.js/#examples) of what can be done with Cube.js such as the [Real-Time Dashboard Guide](https://real-time-dashboard.cube.dev?utm_source=dev-to&utm_medium=post&utm_campaign=react-data-table) and the [Open Source Web Analytics Platform Guide](https://web-analytics.cube.dev?utm_source=dev-to&utm_medium=post&utm_campaign=react-data-table).