React – Material-Table Library with Axios

Photo by Monstera from Pexels

Overview

In this example, we will build a small react app with Material UI, Material-Table, and Axios. The Material-Table library is great option for a feature rich data grid that handles pagination, sorting, filtering, in-line edits, and grouping. The library is well documented like Material UI and has over 100k downloads a week. Using Axios, the app will call randomuser.me to fetch 20 users. You can download the source files from https://github.com/fullstacksoup/blog-react-material-grid-demo.

Prerequisites

First create the React Application.

npx create-react-app your-app-name

Install the following libraries with NPM.

npm install @material-ui/core
npm install @material-ui/icons
npm install material-table
npm install axios

Building the Component

Create a folder labeled Components. Under the Components folder create a component labeled MatDataTable.js and another one labeled DetailTable.js.

The following are pieces for MatDataTable.js

Imports

import React, { Component } from "react";
import axios from 'axios'; // npm instal axios
import { forwardRef } from 'react';
import MaterialTable from "material-table";
import EditIcon from '@material-ui/icons/Edit';
import DeleteIcon from '@material-ui/icons/Delete';
import SaveIcon from '@material-ui/icons/Save';
import ArrowDownward from '@material-ui/icons/ArrowDownward';
import ChevronLeft from '@material-ui/icons/ChevronLeft';
import ChevronRight from '@material-ui/icons/ChevronRight';
import Clear from '@material-ui/icons/Clear';
import FilterList from '@material-ui/icons/FilterList';
import FirstPage from '@material-ui/icons/FirstPage';
import LastPage from '@material-ui/icons/LastPage';
import Search from '@material-ui/icons/Search';
// Import the detail table if you want use the collapsible section
import DetailTable from './DetailTable';  

Table Icons

Create constants for the table icons. These are the icons used throughout Material-Table and must be defined or it will render the initials of the icon.

MatDataTable.js icon definitions.

const tableIcons = {
  Edit: forwardRef((props, ref) => <EditIcon {...props} ref={ref} />),
  Delete: forwardRef((props, ref) => <DeleteIcon {...props} ref={ref} />),
  Check: forwardRef((props, ref) => <SaveIcon {...props} ref={ref} />),
  Clear: forwardRef((props, ref) => <Clear {...props} ref={ref} />),
  DetailPanel: forwardRef((props, ref) => <ChevronRight {...props} ref={ref} />),
  Filter: forwardRef((props, ref) => <FilterList {...props} ref={ref} />),
  FirstPage: forwardRef((props, ref) => <FirstPage {...props} ref={ref} />),
  LastPage: forwardRef((props, ref) => <LastPage {...props} ref={ref} />),
  NextPage: forwardRef((props, ref) => <ChevronRight {...props} ref={ref} />),
  PreviousPage: forwardRef((props, ref) => <ChevronLeft {...props} ref={ref} />),
  ResetSearch: forwardRef((props, ref) => <Clear {...props} ref={ref} />),
  Search: forwardRef((props, ref) => <Search {...props} ref={ref} />),
  SortArrow: forwardRef((props, ref) => <ArrowDownward {...props} ref={ref} />),
};

Component Properties

Create a class component labeled MatDataTable.js, setup the property person[] to hold the random users data from randomuser.me.

export default class MatDataTable extends Component {
  constructor(props) {
    super(props);
    this.state = {person: []};   
  }
    

Fetching Data – API Call

Taking advantage of the class component componentDidMount() event, call randomuser.me and fetch 20 users. Added a couple of console.log() function to see if there is data.

 componentDidMount(prevProps) {    
    const maxResults = 20;    
    const url = `https://randomuser.me/api/?results=${maxResults}`;
    axios.get(url)
    .then(results => {
      console.log(results);
      console.log(results.data.results);
      this.setState({ person: results.data.results });

      var newArr = results.data.results.map(function(val) {          
        return {
          id: val.id.value,
          gender: val.gender,
          login: val.login.username,
          email: val.email,          
          cell: val.cell,          
          image: val.picture.thumbnail,          
          name: val.name.first + ' ' + val.name.last,
        };
      });
      console.log(results.data.results); 
      this.setState({
        tableArray: newArr  //set state of the weather5days
      },()=> {
         console.log(this.state.tableArray); 
         console.log('this.tableArray ',this.state.tableArray);
      });      
    });
  }

Rendering the Table

Finally, render the table with the grouping options set to true. The grouping option allow you to drag and drop a column header as shown in the partial code below.

  <MaterialTable
          icons={tableIcons}
          options={{
            grouping: true
          }}
          detailPanel={rowData => {
            return (              
              <DetailTable />
            )
          }}
.....

The render() section of the component.


  render() {
    return (      
      <div style={{ maxWidth: "50%", marginLeft: "300px", marginTop: "100px" }}>
        <MaterialTable
          icons={tableIcons}
          options={{
            grouping: true
          }}
          detailPanel={rowData => {
            return (              
              <DetailTable />
            )
          }}
          columns={[
            {
              title: 'Image',
              field: 'image',
              render: rowData => (
                <img
                  style={{ height: 36, borderRadius: '50%' }}
                  src={rowData.image}
                />
              ),
            },
            { title: "Name", field: "name", type: "numeric", align: 'left' },            
            { title: "Gender", field: "gender"},            
            { title: "Email", field: "email" },
            { title: "Cell Phone", field: "cell", type: "numeric" }                                     
          ]}
          data={this.state.tableArray}      
        
          title="Demo Title"
        />
      </div>
    );
  }
}

All together – Complete Component:

import React, { Component } from "react";
import axios from 'axios'; // npm instal axios
import { forwardRef } from 'react';
import MaterialTable from "material-table";
import ArrowDownward from '@material-ui/icons/ArrowDownward';
import ChevronLeft from '@material-ui/icons/ChevronLeft';
import ChevronRight from '@material-ui/icons/ChevronRight';
import Clear from '@material-ui/icons/Clear';
import FilterList from '@material-ui/icons/FilterList';
import FirstPage from '@material-ui/icons/FirstPage';
import LastPage from '@material-ui/icons/LastPage';
import Search from '@material-ui/icons/Search';
import DetailTable from './DetailTable';

const tableIcons = {
  DetailPanel: forwardRef((props, ref) => <ChevronRight {...props} ref={ref} />),
  Filter: forwardRef((props, ref) => <FilterList {...props} ref={ref} />),
  FirstPage: forwardRef((props, ref) => <FirstPage {...props} ref={ref} />),
  LastPage: forwardRef((props, ref) => <LastPage {...props} ref={ref} />),
  NextPage: forwardRef((props, ref) => <ChevronRight {...props} ref={ref} />),
  PreviousPage: forwardRef((props, ref) => <ChevronLeft {...props} ref={ref} />),
  ResetSearch: forwardRef((props, ref) => <Clear {...props} ref={ref} />),
  Search: forwardRef((props, ref) => <Search {...props} ref={ref} />),
  SortArrow: forwardRef((props, ref) => <ArrowDownward {...props} ref={ref} />),
};

export default class MatDataTable extends Component {
  constructor(props) {
    super(props);
    this.state = {person: []};   
  }
    
  componentDidMount(prevProps) {    
    const maxResults = 20;    
    const url = `https://randomuser.me/api/?results=${maxResults}`;
    axios.get(url)
    .then(results => {
      console.log(results);
      console.log(results.data.results);
      this.setState({ person: results.data.results });

      var newArr = results.data.results.map(function(val) {          
        return {
          id: val.id.value,
          gender: val.gender,
          login: val.login.username,
          email: val.email,          
          cell: val.cell,          
          image: val.picture.thumbnail,          
          name: val.name.first + ' ' + val.name.last,
        };
      });
      console.log(results.data.results); 
      this.setState({
        tableArray: newArr  //set state of the weather5days
      },()=> {
         console.log(this.state.tableArray); 
         console.log('this.tableArray ',this.state.tableArray);
      });      
    });
  }

  render() {
    return (      
      <div style={{ maxWidth: "50%", marginLeft: "300px", marginTop: "100px" }}>
        <MaterialTable
          icons={tableIcons}
          options={{
            grouping: true
          }}
          detailPanel={rowData => {
            return (              
              <DetailTable />
            )
          }}
          columns={[
            {
              title: 'Image',
              field: 'image',
              render: rowData => (
                <img
                  style={{ height: 36, borderRadius: '50%' }}
                  src={rowData.image}
                />
              ),
            },
            { title: "Name", field: "name", type: "numeric", align: 'left' },            
            { title: "Gender", field: "gender"},            
            { title: "Email", field: "email" },
            { title: "Cell Phone", field: "cell", type: "numeric" }                                     
          ]}
          data={this.state.tableArray}      
        
          title="Demo Title"
        />
      </div>
    );
  }
}
 

Expandable Sections

Add the detail table in the expand and collapse section.

Create another component labeled DetailTable.js and add the following code.

import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableContainer from '@material-ui/core/TableContainer';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
import Paper from '@material-ui/core/Paper';

const useStyles = makeStyles({
  table: {
    minWidth: 650,
  },
});

function createData(name, calories, fat, carbs, protein) {
  return { name, calories, fat, carbs, protein };
}

const rows = [
  createData('Frozen yoghurt', 159, 6.0, 24, 4.0),
  createData('Ice cream sandwich', 237, 9.0, 37, 4.3),
  createData('Eclair', 262, 16.0, 24, 6.0),
  createData('Cupcake', 305, 3.7, 67, 4.3),
  createData('Gingerbread', 356, 16.0, 49, 3.9),
];

export default function DetailTable() {
  const classes = useStyles();

  return (
    <TableContainer component={Paper}>
      <Table className={classes.table} aria-label="simple table">
        <TableHead>
          <TableRow>
            <TableCell>Dessert (100g serving)</TableCell>
            <TableCell align="right">Calories</TableCell>
            <TableCell align="right">Fat&nbsp;(g)</TableCell>
            <TableCell align="right">Carbs&nbsp;(g)</TableCell>
            <TableCell align="right">Protein&nbsp;(g)</TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          {rows.map((row) => (
            <TableRow key={row.name}>
              <TableCell component="th" scope="row">
                {row.name}
              </TableCell>
              <TableCell align="right">{row.calories}</TableCell>
              <TableCell align="right">{row.fat}</TableCell>
              <TableCell align="right">{row.carbs}</TableCell>
              <TableCell align="right">{row.protein}</TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </TableContainer>
  );
}

Routing (Optional)

If you already have routing and navigation in your app ignore the rest of this post.

If you just want to see the table without routing, then you can do the following.

In the App.js component update the code to the following,

import React, { Component } from 'react';
import './App.css';
import MatDataTable from './Components/MatDataTable';

class App extends Component {  

  render() {
    const spacing = 5;
    return (      
      <div className="App" >              
         <MatDataTable />
      </div>
    );
  }
}

export default App;

TIPRender HTML Value

I do not recommend the following If your JSON data has HTML then you can do the following to render it properly.

Using dangerouslySetInnerHTML

columns={[
   { title: "Title", field: "Title", align: "left",
      render: rowData => (
         <div dangerouslySetInnerHTML={{ __html: rowData.Title }} /> 
      ),
   },
   { title: "Company", field: "Company"},            
   { title: "PostDate", field: "PostDate", type: "date"  },
...

Since this can be a security risk, I will try to come up with a better solution and post it here.