React Table example: CRUD App | react-table 7

In this tutorial, I will show you how to build a React Table example with react-table 7 by a CRUD Application to consume Web API with Hooks, Axios, display data table and modify with Router & Bootstrap.

Fullstack:
React + Spring Boot + MySQL: CRUD example
React + Spring Boot + PostgreSQL: CRUD example
React + Spring Boot + MongoDB: CRUD example
React + Node.js + Express + MySQL: CRUD example
React + Node.js + Express + PostgreSQL example
React + Node.js + Express + MongoDB example
React + Django + Rest Framework example

Related Posts:
React Custom Hook
React Form Validation with Hooks example
React Hooks File Upload example with Axios & Progress Bar
React Hooks: JWT Authentication (without Redux) example
React Hooks + Redux: JWT Authentication example

Serverless with Firebase:
React Hooks + Firebase Realtime Database: CRUD App
React Hooks + Firestore example: CRUD app


Overview of React Table example CRUD with Web API

We will build a React Table Tutorial Application in that:

  • Each Tutorial has id, title, description, published status.
  • We can create, retrieve, update, delete Tutorials.
  • List of Tutorials is shown in a Table using react-table 7
  • There is a Search bar for finding Tutorials by title.

Here are screenshots of our React.js CRUD Application.

– Create a Tutorial:

react-table-example-crud-react-table-v7-create-tutorial

– Retrieve all Tutorials with a data table:

react-table-example-crud-react-table-v7-retrieve-tutorial

– Click on Edit button to update an item:

react-table-example-crud-react-table-v7-retrieve-one-tutorial

On this Page, you can:

  • change status to Published using Publish button
  • delete the item using Delete button
  • update the item details with Update button

react-table-example-crud-react-table-v7-update-tutorial

For Form Validation, please visit:
React Form Validation with Hooks example

– Search Tutorials by title:

react-table-example-crud-react-table-v7-search-tutorial

This React-Table App Client consumes the following Web API:

Methods Urls Actions
POST /api/tutorials create new Tutorial
GET /api/tutorials retrieve all Tutorials
GET /api/tutorials/:id retrieve a Tutorial by :id
PUT /api/tutorials/:id update a Tutorial by :id
DELETE /api/tutorials/:id delete a Tutorial by :id
DELETE /api/tutorials delete all Tutorials
GET /api/tutorials?title=[keyword] find all Tutorials which title contains keyword

You can find step by step to build a Server like this in one of these posts:
Express, Sequelize & MySQL
Express, Sequelize & PostgreSQL
Express, Sequelize & SQL Server
Express & MongoDb
Spring Boot & MySQL
Spring Boot & PostgreSQL
Spring Boot & MongoDB
Spring Boot & SQL Server
Spring Boot & H2
Spring Boot & Cassandra
Spring Boot & Oracle
Python/Django & MySQL
Python/Django & PostgreSQL
Python/Django & MongoDB

React App Diagram with Axios and Router

Let’s see the React Application Diagram that we’re gonna implement:

react-table-example-crud-react-table-v7-components-diagram

– The App component is a container with React Router. It has navbar that links to routes paths.

TutorialsList gets and displays Tutorials in table using react-table v7.
Tutorial has form for editing Tutorial’s details based on :id.
AddTutorial has form for submission new Tutorial.

– They call TutorialDataService functions which use axios to make HTTP requests and receive responses.

Technology

  • React 16/17
  • react-table 7.6.3
  • react-router-dom 5.2.0
  • axios 0.21.1
  • bootstrap 4
  • fontawesome-free 5

Project Structure

Now look at the project directory structure:

react-table-example-crud-react-table-v7-tutorial-project-structure

Let me explain it briefly.

package.json contains 4 main modules: react, react-router-dom, axios & bootstrap.
App is the container that has Router & navbar.
– There are 3 items using React Hooks: TutorialsList, Tutorial, AddTutorial.
http-common.js initializes axios with HTTP base Url and headers.
TutorialService has functions for sending HTTP requests to the Apis.
.env configures port for this React Table CRUD App.

Setup React.js Project

Open cmd at the folder you want to save Project folder, run command:
npx create-react-app react-table-crud-example

After the process is done. We create additional folders and files like the following tree:


public

src

components

AddTutorial.js

Tutorial.js

TutorialsList.js

services

TutorialService.js

App.css

App.js

index.js

package.json


Install Bootstrap, FontAwesome for React Table CRUD App

Run command: npm install bootstrap @fortawesome/fontawesome-free.

Open src/App.js and modify the code inside it as following-

import React, { Component } from "react";
import "bootstrap/dist/css/bootstrap.min.css";
import "@fortawesome/fontawesome-free/css/all.css";
import "@fortawesome/fontawesome-free/js/all.js";

class App extends Component {
  render() {
    // ...
  }
}

export default App;

Add React Router to React Table CRUD App

– Run the command: npm install react-router-dom.
– Open src/index.js and wrap App component by BrowserRouter object.

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";

import App from "./App";
import reportWebVitals from './reportWebVitals';

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById("root")
);

reportWebVitals();

Add Navbar to React Table CRUD App

Open src/App.js, this App component is the root container for our application, it will contain a navbar, and also, a Switch object with several Route. Each Route points to a React Component.

import React from "react";
import { Switch, Route, Link } from "react-router-dom";
...

import AddTutorial from "./components/AddTutorial";
import Tutorial from "./components/Tutorial";
import TutorialsList from "./components/TutorialsList";

function App() {
  return (
    <div>
      <nav className="navbar navbar-expand navbar-dark bg-dark">
        <a href="/tutorials" className="navbar-brand">
          bezKoder
        </a>
        <div className="navbar-nav mr-auto">
          <li className="nav-item">
            <Link to={"/tutorials"} className="nav-link">
              Tutorials
            </Link>
          </li>
          <li className="nav-item">
            <Link to={"/add"} className="nav-link">
              Add
            </Link>
          </li>
        </div>
      </nav>

      <div className="container mt-3">
        <Switch>
          <Route exact path={["/", "/tutorials"]} component={TutorialsList} />
          <Route exact path="/add" component={AddTutorial} />
          <Route path="/tutorials/:id" component={Tutorial} />
        </Switch>
      </div>
    </div>
  );
}

export default App;

You can simplify import statement with:
Absolute Import in React

Initialize Axios for sending HTTP requests

Let’s install axios with command: npm install axios.
Under src folder, we create http-common.js file with following code:

import axios from "axios";

export default axios.create({
  baseURL: "http://localhost:8080/api",
  headers: {
    "Content-type": "application/json"
  }
});

You can change the baseURL that depends on REST APIs url that your Server configures.

Create Data Service

In this step, we’re gonna create a service that uses axios object above to send HTTP requests.
The service exports CRUD functions and finder method:

  • CREATE: create
  • RETRIEVE: getAll, get
  • UPDATE: update
  • DELETE: remove, removeAll
  • FINDER: findByTitle

services/TutorialService.js

import http from "../http-common";

const getAll = () => {
  return http.get("/tutorials");
};

const get = (id) => {
  return http.get(`/tutorials/${id}`);
};

const create = (data) => {
  return http.post("/tutorials", data);
};

const update = (id, data) => {
  return http.put(`/tutorials/${id}`, data);
};

const remove = (id) => {
  return http.delete(`/tutorials/${id}`);
};

const removeAll = () => {
  return http.delete(`/tutorials`);
};

const findByTitle = (title) => {
  return http.get(`/tutorials?title=${title}`);
};

const TutorialService = {
  getAll,
  get,
  create,
  update,
  remove,
  removeAll,
  findByTitle,
};

export default TutorialService;

We call axios (imported as http) get, post, put, delete method corresponding to HTTP Requests: GET, POST, PUT, DELETE to make CRUD Operations.

Create React Components

Now we’re gonna build 3 components corresponding to 3 Routes defined before.

Add Object

This component has a Form to submit new Tutorial with 2 fields: title & description.

react-table-example-crud-react-table-v7-create-tutorial

components/AddTutorial.js

import React, { useState } from "react";
import TutorialDataService from "../services/TutorialService";

const AddTutorial = () => {
  const initialTutorialState = {
    id: null,
    title: "",
    description: "",
    published: false
  };
  const [tutorial, setTutorial] = useState(initialTutorialState);
  const [submitted, setSubmitted] = useState(false);

  const handleInputChange = event => {
    const { name, value } = event.target;
    setTutorial({ ...tutorial, [name]: value });
  };

  const saveTutorial = () => {
    var data = {
      title: tutorial.title,
      description: tutorial.description
    };

    TutorialDataService.create(data)
      .then(response => {
        setTutorial({
          id: response.data.id,
          title: response.data.title,
          description: response.data.description,
          published: response.data.published
        });
        setSubmitted(true);
        console.log(response.data);
      })
      .catch(e => {
        console.log(e);
      });
  };

  const newTutorial = () => {
    setTutorial(initialTutorialState);
    setSubmitted(false);
  };

  return (
    // ...
  );
};

export default AddTutorial;

First, we define and set initial state: tutorial & submitted.

Next, we create handleInputChange() function to track the values of the input and set that state for changes. We also have a function to get tutorial state and send the POST request to the Web API. It calls TutorialDataService.create() method.

For return, we check the submitted state, if it is true, we show Add button for creating new Tutorial again. Otherwise, a Form with Submit button will display.

const AddTutorial = () => {
  ...

  return (
    <div className="submit-form">
      {submitted ? (
        <div>
          <h4>You submitted successfully!</h4>
          <button className="btn btn-success" onClick={newTutorial}>
            Add
          </button>
        </div>
      ) : (
        <div>
          <div className="form-group">
            <label htmlFor="title">Title</label>
            <input
              type="text"
              className="form-control"
              id="title"
              required
              value={tutorial.title}
              onChange={handleInputChange}
              name="title"
            />
          </div>

          <div className="form-group">
            <label htmlFor="description">Description</label>
            <input
              type="text"
              className="form-control"
              id="description"
              required
              value={tutorial.description}
              onChange={handleInputChange}
              name="description"
            />
          </div>

          <button onClick={saveTutorial} className="btn btn-success">
            Submit
          </button>
        </div>
      )}
    </div>
  );
};

export default AddTutorial;

React Table for List of Objects Page

There will be:

  • a search bar for finding Tutorials by title.
  • a tutorials array displayed as a table.
  • two buttons for edit & remove actions shown on the right of each row.

react-table-example-crud-react-table-v7-retrieve-tutorial

So we will have following state:

  • searchTitle
  • tutorials

We also need to use 4 TutorialDataService methods:

  • getAll()
  • findByTitle()
  • delete()
  • deleteAll()

We’re gonna use the Effect Hook: useEffect() to fetch the data from the Web API. This Hook tells React that the component needs to do something after render or performing the DOM updates. In this effect, we perform data fetching from API.

components/TutorialsList.js

import React, { useState, useEffect, useMemo, useRef } from "react";
import TutorialDataService from "../services/TutorialService";
...

const TutorialsList = (props) => {
  const [tutorials, setTutorials] = useState([]);
  const [searchTitle, setSearchTitle] = useState("");
  const tutorialsRef = useRef();

  tutorialsRef.current = tutorials;

  useEffect(() => {
    retrieveTutorials();
  }, []);

  const onChangeSearchTitle = (e) => {
    const searchTitle = e.target.value;
    setSearchTitle(searchTitle);
  };

  const retrieveTutorials = () => {
    TutorialDataService.getAll()
      .then((response) => {
        setTutorials(response.data);
      })
      .catch((e) => {
        console.log(e);
      });
  };

  const refreshList = () => {
    retrieveTutorials();
  };

  const removeAllTutorials = () => {
    TutorialDataService.removeAll()
      .then((response) => {
        console.log(response.data);
        refreshList();
      })
      .catch((e) => {
        console.log(e);
      });
  };

  const findByTitle = () => {
    TutorialDataService.findByTitle(searchTitle)
      .then((response) => {
        setTutorials(response.data);
      })
      .catch((e) => {
        console.log(e);
      });
  };

  const openTutorial = (rowIndex) => {
    const id = tutorialsRef.current[rowIndex].id;

    props.history.push("/tutorials/" + id);
  };

  const deleteTutorial = (rowIndex) => {
    const id = tutorialsRef.current[rowIndex].id;

    TutorialDataService.remove(id)
      .then((response) => {
        props.history.push("/tutorials");

        let newTutorials = [...tutorialsRef.current];
        newTutorials.splice(rowIndex, 1);

        setTutorials(newTutorials);
      })
      .catch((e) => {
        console.log(e);
      });
  };

  return (
    ...
  );
};

export default TutorialsList;

Let’s continue to implement the UI.

This is where we use react-table for displaying tabular data. In addition to tutorial’s fields (title, description, status), we also have Actions column with edit & delete icon buttons.

Install react-table with command: npm install react-table.

Similar to any table, a React Table includes columns and data:

  • columns: array of columns which act as header groups. The columns can be recursively nested as much as needed.
  • data: array of rows to be displayed on the table.
const columns = [
  {
    Header: "Title",
    accessor: "title",
  },
  {
    Header: "Description",
    accessor: "description",
  },
  ...
];

const data = [
  {
    title: "bezkoder Tut#1",
    description: "description Tut#1",
  },
  {
    title: "bezkoder Tut#2",
    description: "description Tut#2",
  },
  ...
];

Now, how to modify the value of the cell, for example:

  • if cell value is true/false, change it to 'Published'/'Pending'
  • set a custom html element with icons and button click events

react-table supports the way to work with Cell property like this:

const columns = [
  ...,
  {
    Header: "Status",
    accessor: "published",
    Cell: (props) => {
      return props.value ? "Published" : "Pending";
    },
  },
  {
    Header: "Actions",
    accessor: "actions",
    Cell: (props) => {
      const rowIdx = props.row.id;
      return (
        <div>
          <span onClick={() => openTutorial(rowIdx)}>
            <i className="far fa-edit action mr-2"></i>
          </span>

          <span onClick={() => deleteTutorial(rowIdx)}>
            <i className="fas fa-trash action"></i>
          </span>
        </div>
      );
    },
  }
];

const data = [
  {
    title: "bezkoder Tut#1",
    description: "description Tut#1",
    published: true
  },
  {
    title: "bezkoder Tut#2",
    description: "description Tut#2",
    published: false
  },
  ...
];

If you click on edit icon button of any Tutorial, the app will call openTutorial() method and direct you to Tutorial page. Similar for trash icon button, deleteTutorial() will be invoked.

Now we’re gonna use a custom hook that react-table provides – useTable() – which implements many features: row sorting, filtering, searching, pagination, row selection, infinity scrolling…

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

useTable() takes columns and data to build a table instance.

We use several props from the instance:

  • getTableProps() function is called inside the table tags to resolve any props needed by the table wrapper which built-in props is {role: "table"}.
  • getTableBodyProps() function is called inside the tbody tags resolves any props needed by the table body wrapper which built-in props is {role: “rowgroup”}.
  • prepareRow() function must be called on any rows to be displayed. It is responsible for lazily preparing a row for rendering.
  • headerGroups and rows are internal data structures derived from columns and data above.

This is full code for building the UI and table:

...
import { useTable } from "react-table";

  ...
  const columns = useMemo(
    () => [
      {
        Header: "Title",
        accessor: "title",
      },
      {
        Header: "Description",
        accessor: "description",
      },
      {
        Header: "Status",
        accessor: "published",
        Cell: (props) => {
          return props.value ? "Published" : "Pending";
        },
      },
      {
        Header: "Actions",
        accessor: "actions",
        Cell: (props) => {
          const rowIdx = props.row.id;
          return (
            <div>
              <span onClick={() => openTutorial(rowIdx)}>
                <i className="far fa-edit action mr-2"></i>
              </span>

              <span onClick={() => deleteTutorial(rowIdx)}>
                <i className="fas fa-trash action"></i>
              </span>
            </div>
          );
        },
      },
    ],
    []
  );

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

  return (
    <div className="list row">
      <div className="col-md-8">
        <div className="input-group mb-3">
          <input
            type="text"
            className="form-control"
            placeholder="Search by title"
            value={searchTitle}
            onChange={onChangeSearchTitle}
          />
          <div className="input-group-append">
            <button
              className="btn btn-outline-secondary"
              type="button"
              onClick={findByTitle}
            >
              Search
            </button>
          </div>
        </div>
      </div>
      <div className="col-md-12 list">
        <table
          className="table table-striped table-bordered"
          {...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>
      </div>

      <div className="col-md-8">
        <button className="btn btn-sm btn-danger" onClick={removeAllTutorials}>
          Remove All
        </button>
      </div>
    </div>
  );
};

export default TutorialsList;

Object details Component

For getting data & update, delete the Tutorial, this component will use 3 TutorialDataService functions:

  • get()
  • update()
  • remove()

We also use the Effect Hook useEffect() to get Tutorial by id in the URL.

components/Tutorial.js

import React, { useState, useEffect } from "react";
import TutorialDataService from "../services/TutorialService";

const Tutorial = props => {
  const initialTutorialState = {
    id: null,
    title: "",
    description: "",
    published: false
  };
  const [currentTutorial, setCurrentTutorial] = useState(initialTutorialState);
  const [message, setMessage] = useState("");

  const getTutorial = id => {
    TutorialDataService.get(id)
      .then(response => {
        setCurrentTutorial(response.data);
        console.log(response.data);
      })
      .catch(e => {
        console.log(e);
      });
  };

  useEffect(() => {
    getTutorial(props.match.params.id);
  }, [props.match.params.id]);

  const handleInputChange = event => {
    const { name, value } = event.target;
    setCurrentTutorial({ ...currentTutorial, [name]: value });
  };

  const updatePublished = status => {
    var data = {
      id: currentTutorial.id,
      title: currentTutorial.title,
      description: currentTutorial.description,
      published: status
    };

    TutorialDataService.update(currentTutorial.id, data)
      .then(response => {
        setCurrentTutorial({ ...currentTutorial, published: status });
        console.log(response.data);
        setMessage("The status was updated successfully!");
      })
      .catch(e => {
        console.log(e);
      });
  };

  const updateTutorial = () => {
    TutorialDataService.update(currentTutorial.id, currentTutorial)
      .then(response => {
        console.log(response.data);
        setMessage("The tutorial was updated successfully!");
      })
      .catch(e => {
        console.log(e);
      });
  };

  const deleteTutorial = () => {
    TutorialDataService.remove(currentTutorial.id)
      .then(response => {
        console.log(response.data);
        props.history.push("/tutorials");
      })
      .catch(e => {
        console.log(e);
      });
  };

  return (
    ...
  );
};

export default Tutorial;

And this is the code inside return:

const Tutorial = props => {
  ...

  return (
    <div>
      {currentTutorial ? (
        <div className="edit-form">
          <h4>Tutorial</h4>
          <form>
            <div className="form-group">
              <label htmlFor="title">Title</label>
              <input
                type="text"
                className="form-control"
                id="title"
                name="title"
                value={currentTutorial.title}
                onChange={handleInputChange}
              />
            </div>
            <div className="form-group">
              <label htmlFor="description">Description</label>
              <input
                type="text"
                className="form-control"
                id="description"
                name="description"
                value={currentTutorial.description}
                onChange={handleInputChange}
              />
            </div>

            <div className="form-group">
              <label>
                <strong>Status:</strong>
              </label>
              {currentTutorial.published ? "Published" : "Pending"}
            </div>
          </form>

          {currentTutorial.published ? (
            <button
              className="badge badge-primary mr-2"
              onClick={() => updatePublished(false)}
            >
              UnPublish
            </button>
          ) : (
            <button
              className="badge badge-primary mr-2"
              onClick={() => updatePublished(true)}
            >
              Publish
            </button>
          )}

          <button className="badge badge-danger mr-2" onClick={deleteTutorial}>
            Delete
          </button>

          <button
            type="submit"
            className="badge badge-success"
            onClick={updateTutorial}
          >
            Update
          </button>
          <p>{message}</p>
        </div>
      ) : (
        <div>
          <br />
          <p>Please click on a Tutorial...</p>
        </div>
      )}
    </div>
  );
};

export default Tutorial;

Add CSS style for React Components

Open src/App.css and write CSS code as following:

.list .action {
  cursor: pointer;
}

.submit-form {
  max-width: 300px;
  margin: auto;
}

.edit-form {
  max-width: 300px;
  margin: auto;
}

Configure Port for React Table CRUD with Web API

Because most of HTTP Server use CORS configuration that accepts resource sharing retrictted to some sites or ports, so we also need to configure port for our App.

In project folder, create .env file with following content:

PORT=8081

Now we’ve set our app running at port 8081.

Run React Table CRUD App

You can run our App with command: npm start.
If the process is successful, open Browser with Url: http://localhost:8081/ and check it.

This React Client will work well with following back-end Rest APIs:
Express, Sequelize & MySQL
Express, Sequelize & PostgreSQL
Express, Sequelize & SQL Server
Express & MongoDb
Spring Boot & MySQL
Spring Boot & PostgreSQL
Spring Boot & MongoDB
Spring Boot & SQL Server
Spring Boot & H2
Spring Boot & Cassandra
Spring Boot & Oracle
Python/Django & MySQL
Python/Django & PostgreSQL
Python/Django & MongoDB

Conclusion

Today we’ve built a React Table CRUD CRUD example successfully with react-table 7, Hooks, Axios & React Router. Now we can consume REST APIs, display data table, search and modify data in a clean way. I hope you apply it in your project at ease.

For larger data, you may need to make pagination:
React Pagination using Hooks example

You can also find how to implement Authentication & Authorization with following posts:
React Hooks: JWT Authentication (without Redux) example
React Hooks + Redux: JWT Authentication example

Or File upload example:
React Hooks File Upload example with Axios & Progress Bar

Serverless with Firebase:
React Hooks + Firebase Realtime Database: CRUD App
React Hooks + Firestore example: CRUD app

Happy learning, see you again!

Further Reading

Integration:
Integrate React with Spring Boot
Integrate React with Node.js Express

Table Pagination:
React Table Pagination (Server side) with Search | react-table v7

react-table-pagination-server-side-with-search-example

Source Code

You can find the complete source code for this tutorial on Github.

9 thoughts to “React Table example: CRUD App | react-table 7”

  1. Excellent tutorial, just one question: Why did you use tutorialsRef in TutorialsList and not just tutorials?
    (I understand that sometimes we can use ref inside a useMemo to save its value between re-renders but here you didn’t use it in it…)

    1. Ok I think I got it, correct me if I’m wrong but, because useMemo() in TutorialsList is only invoked once, it only has a reference to openTutorial() (with tutorials inside let’s assume) with tutorials remembered as an outer scope variable – so basically a constant equal to an empty array ([]).
      So that’s why we use TutorialsRef.current instead, so we are always synched with tutorials (a live tutorials variable).

  2. Great tutorials. But I am new to react, I can not get the frontend and backend integrated as expected.
    the front end is https://www.bezkoder.com/react-table-example-hooks-crud/,
    the back end is express mongodb: https://www.bezkoder.com/node-express-mongodb-crud-rest-api/
    the both work well independently.
    How should I integrate them to run on one same server?
    I followed the instruction for integration, but it looks like the db.sequelize.sync() was applied. If I comment db.sequelize.sync out, the two parts were running on one same server, but can not connect to mongo database.
    Can you show me what the server.js should be for integration of these two parts?

  3. combining the finest, perhaps you can make a react-hooks-redux-crud with react-table, using node-js-express-sequelize-mysql/….
    Thanks for all your tutorials, so very helpful!

      1. I got a question on the Route part

        Can i use the same for display the data but with different purpose such as display page and edit page

Comments are closed to reduce spam. If you have any question, please send me an email.