How to Make a Table in React using Hooks with Multiple Features

Share your love

We all know how important tables are and we all have made them with different UI’s in HTML, CSS, and JS. But today, we will be making the same table in React without using any other library or npm packages for any operation that our table will perform.

Let’s begin…

Index

  1. Getting Started
  2. Creating the App – React Table
  3. Working on the UI part of the App
  4. Using useState hook for defining and managing states 
  5. Making the Form Component for adding a new row
  6. Working on functionalities
    • Deleting a row
    • Editing a row
    • Saving the Edited row
    • Sorting the table 
    • Pagination 
    • Filtration

Getting Started

We have made a dummy data for this project consisting of 10 objects with 6 key and value pairs as shown and named the file data.js.

export const data = [
  {
    id: 1,
    fullName: "Leanne Graham",
    userName: "Bret",
    email: "Sincere@april.biz",
    phoneNumber: "1-770-736-8031 x56442",
    website: "hildegard.org",
    companyName: "Romaguera-Crona",
  },
  {
    id: 2,
    fullName: "Ervin Howell",
    userName: "Antonette",
    email: "Shanna@melissa.tv",
    phoneNumber: "010-692-6593 x09125",
    website: "anastasia.net",
    companyName: "Deckow-Crist",
  },
  {
    id: 3,
    fullName: "Clementine Bauch",
    userName: "Samantha",
    email: "Nathan@yesenia.net",
    phoneNumber: "1-463-123-4447",
    website: "ramiro.info",
    companyName: "Romaguera-Jacobson",
  },
  {
    id: 4,
    fullName: "Patricia Lebsack",
    userName: "Karianne",
    email: "Julianne.OConner@kory.org",
    phoneNumber: "493-170-9623 x156",
    website: "kale.biz",
    companyName: "Robel-Corkery",
  },
  {
    id: 5,
    fullName: "Chelsey Dietrich",
    userName: "Kamren",
    email: "Lucio_Hettinger@annie.ca",
    phoneNumber: "(254)954-1289",
    website: "demarco.info",
    companyName: "Keebler LLC",
  },
  {
    id: 6,
    fullName: "Mrs. Dennis Schulist",
    userName: "Leopoldo_Corkery",
    email: "Karley_Dach@jasper.info",
    phoneNumber: "1-477-935-8478 x6430",
    website: "ola.org",
    companyName: "Considine-Lockman",
  },
  {
    id: 7,
    fullName: "Kurtis Weissnat",
    userName: "Elwyn.Skiles",
    email: "Telly.Hoeger@billy.biz",
    phoneNumber: "210.067.6132",
    website: "elvis.io",
    companyName: "Johns Group",
  },
  {
    id: 8,
    fullName: "Nicholas Runolfsdottir V",
    userName: "Maxime_Nienow",
    email: "Sherwood@rosamond.me",
    phoneNumber: "586.493.6943 x140",
    website: "jacynthe.com",
    companyName: "Abernathy Group",
  },
  {
    id: 9,
    fullName: "Glenna Reichert",
    userName: "Delphine",
    email: "Chaim_McDermott@dana.io",
    phoneNumber: "(775)976-6794 x41206",
    website: "conrad.com",
    companyName: "Yosting Mantra",
  },
  {
    id: 10,
    fullName: "Clementina DuBuque",
    userName: "Moriah.Stanton",
    email: "Rey.Padberg@karina.biz",
    phoneNumber: "024-648-3804",
    website: "ambrose.net",
    companyName: "Hoeger LLC",
  },
];

Before we start the development, let’s see what the final result will look like.

We will not discuss the styling part of the app for that visit my Github repository 

Creating our React App

It’s easy to create a React App – go to the working directory in any IDE and enter the following command in the terminal:

npx create-react-app table-app-in-react

If you are unsure how to properly set up a create-react-app project you can refer to the official guide here at create-react-app-dev.‌‌

After the setup, run npm start in the same terminal to start the localhost:3000 where our React app will be hosted. We can also see all our changes there.

Working on the UI part of the app

For starters, let’s create a table with all the headings as per the dummy JSON data’s keys and print its values dynamically using the map() function.

import React, { useState } from "react";
import { data } from "./Data";
export const App = () => {
  return (
    <>
      <div className="container-fluid">
        <div className="row pt-5">
          <form>
            <table className="table table-striped table-primary table-hover text-center fs-5 table-bordered border-dark">
              <thead>
                <tr>
                  <th id="tr" onClick={() => Sort("fullName")}>
                    Name
                  </th>
                  <th id="tr" onClick={() => Sort("userName")}>
                    User Name
                  </th>
                  <th id="tr" onClick={() => Sort("phoneNumber")}>
                    Phone Number
                  </th>
                  <th id="tr" onClick={() => Sort("website")}>
                    Website
                  </th>
                  <th id="tr" onClick={() => Sort("companyName")}>
                    Company Name
                  </th>
                  <th id="tr" onClick={() => Sort("email")}>
                    Email
                  </th>
                  <th id="tr">Action</th>
                </tr>
              </thead>
              <tbody>
                {data.map((data) => {
                  return (
                    <>
                      <tr>
                        <td>{data.fullName}</td>
                        <td>{data.userName}</td>
                        <td>{data.phoneNumber}</td>
                        <td>{data.website}</td>
                        <td>{data.companyName}</td>
                        <td>{data.email}</td>
                        <td className="d-flex p-4">
                          <button className="btn btn-dark me-3">
                            <i class="fa-solid fa-pen-to-square"></i>
                          </button>
                          <button className="btn btn-danger">
                            <i class="fa-solid fa-trash"></i>
                          </button>
                        </td>
                      </tr>
                    </>
                  );
                })}
              </tbody>
            </table>
          </form>
        </div>
      </div>
    </>
  );
};

In the above code, we can see two buttons with onClick event handlers. They will be responsible for editing and deleting rows respectively.

table in React
UI Table

Using the useState hook for defining and managing states 

We have made different states for each operation that the table will perform.

Also, readuseState Hook in React JS

// input data for table
const [tableData, setTableData] = useState(data);
//for data ids
const [toggle, setToggle] = useState(null);
//for filtering data
const [search, setSearch] = useState("");
// for editing data
const [editData, setEditData] = useState({
  fullName: "",
  userName: "",
  address: "",
  phoneNumber: "",
  email: "",
  website: "",
  companyName: "",
});
//for sorting
const [order, setOrder] = useState("asc");
// for pagination
// No of pages
const [number, setNumber] = useState(1);
//Number of Posts per page
const [postPerPage] = useState(4);

Making the Form Component for adding a New Row 

UI part of the Form Component

We will be defining another useState hook here because we need to use the value attribute and onChange event handler to store the entered data.

const [newData, setNewData] = useState({
  fullName: "",
  userName: "",
  address: "",
  phoneNumber: "",
  email: "",
  website: "",
  companyName: "",
});

Our table consists of six columns, so we also need a six-columns here and a button that will trigger the add function by adding a new row with the details filled in the input.

The new row thus formed will be added at the very end of the table.

<h1 className="mx-3 mt-4">Add a contact</h1>
<Form tableData={tableData} setTableData={setTableData} />
mport React from "react";
const Form = ({ tableData, setTableData }) => {
  return (
    <>
      <div className="container-fluid">
        <div className="row py-3">
          <form className="d-flex flex-wrap justify-content-center form">
            <input
              type="text"
              name="fullName"
              required
              placeholder="Enter full name..."
              onChange={Change}
              value={newData.fullName}
              className="form-control-sm fs-4 col-3 mx-5"
            />
            <input
              type="text"
              name="userName"
              required
              placeholder="Enter user name..."
              onChange={Change}
              value={newData.userName}
              className="form-control-sm col-3 mx-5 fs-4"
            />
            <input
              type="text"
              name="phoneNumber"
              required
              placeholder="Enter a phone number..."
              onChange={Change}
              value={newData.phoneNumber}
              className="form-control-sm col-3 mx-5 fs-4"
            />
            <input
              type="text"
              name="website"
              required
              placeholder="Enter website..."
              onChange={Change}
              value={newData.website}
              className="form-control-sm col-3 mx-5 fs-4 mt-4"
            />
            <input
              type="text"
              name="companyName"
              required
              placeholder="Enter company name..."
              onChange={Change}
              value={newData.companyName}
              className="form-control-sm col-3 mx-5 fs-4 mt-4"
            />
            <input
              type="email"
              name="email"
              required
              placeholder="Enter an email..."
              onChange={Change}
              value={newData.email}
              className="form-control-sm col-3 mx-5 fs-4 mt-4"
            />
            <div className="w-100"></div>
            <button
              className="btn-primary px-2 mx-3 fs-3 col-2 mt-4"
              onClick={Add}
              type="submit"
            >
              Add
            </button>
          </form>
        </div>
      </div>
    </>
  );
};
 
export default Form;
 

 We will get something like this – 

Adding the details in the table from the Form Component 

const Change = (e) => {
  const { name, value } = e.target;
  setNewData({ ...newData, [name]: value });
};

const Add = (e) => {
  e.preventDefault();
  setTableData([...tableData, { ...newData, id: Math.random() }]); // for giving the new data some random id
  setNewData({
    // for enptying the form
    fullName: "",
    userName: "",
    address: "",
    phoneNumber: "",
    email: "",
    website: "",
    companyName: "",
  });
};

Working on Other Functionalities

Delete Functionality

For activating the delete button of the table, we have to add an onClick event which will trigger the Delete() function. 

<button className="btn btn-danger" onClick={() => Delete(data.id)}>
  <i className="fa-solid fa-trash"></i>
</button>;
const Delete = (del) => {
  const delData = tableData.filter((tbd) => {
    return del !== tbd.id;
  });
  setTableData(delData);
};

const Edit = (data) => {
  // for opening the editable row
  setToggle(data);
  setEditData(data);
};

Edit Functionality

We have to add an onClick event which will trigger the Edit()  function. 

<button className="btn btn-dark me-3" onClick={() => Edit(data)}>
  <i className="fa-solid fa-pen-to-square"></i>
</button>;

Everything we have covered so far was super easy, but the hard part is about to begin.

We will be making a new table for editing the data and sending it back to the initial table for displaying it.

We will divide the UI of the App into two components, and pass the states defined at the top as props from each of them.

We will be using the toggle state for this which we have already defined above for targeting the ids of individual rows, and the ternary operator to toggle between the two components.

{
  tableData.map((data) => {
    return (
      <>
        {toggle === data ? (
          <EditRow
            tableData={tableData}
            setTableData={setTableData}
            setEditData={setEditData}
            editData={editData}
          />
        ) : (
          <ReadOnly data={data} Edit={Edit} Delete={Delete} />
        )}
      </>
    );
  });
}

Working on the UI of the EditRow Component

import React from "react";
const EditRow = ({
  tableData,
  Cancel,
  setTableData,
  editData,
  setEditData,
}) => {
  return (
    <>
      <tr>
        <td>
          <input
            type="text"
            name="fullName"
            required
            placeholder="Enter a name..."
            onChange={handleEditChange}
            value={editData.fullName}
          />
        </td>
        <td>
          <input
            type="text"
            name="userName"
            required
            placeholder="Enter user name..."
            onChange={handleEditChange}
            value={editData.userName}
          />
        </td>
        <td>
          <input
            type="text"
            name="phoneNumber"
            required
            placeholder="Enter a phone number..."
            onChange={handleEditChange}
            value={editData.phoneNumber}
          />
        </td>
        <td>
          <input
            type="text"
            name="website"
            required
            placeholder="Enter website..."
            onChange={handleEditChange}
            value={editData.website}
          />
        </td>
        <td>
          <input
            type="text"
            name="companyName"
            required
            placeholder="Enter company name..."
            onChange={handleEditChange}
            value={editData.companyName}
          />
        </td>
        <td>
          <input
            type="email"
            name="email"
            required
            placeholder="Enter an email..."
            onChange={handleEditChange}
            value={editData.email}
          />
        </td>
        <td className="d-flex justify-content-center">
          <button className="btn btn-success" type="submit" onClick={Save}>
            <i class="fa-solid fa-floppy-disk"></i>
          </button>
          <button className="btn btn-danger" type="button" onClick={Cancel}>
            <i class="fa-solid fa-ban"></i>
          </button>
        </td>
      </tr>
    </>
  );
};

export default EditRow;

We already have the value attributes and onChange in the above code for 

  1. Displaying the initial values of each column of the row triggered due to the Edit button.
  2. Updating and saving the values in editData state.
  3. Sending this updated state back to the ReadOnly Component for displaying 

We have also placed an onClick event handler to trigger a set of different functions related to editing – Save and Cancel that performs the operations as their name. 

Saving the updated Row

const handleEditChange = (e) => {
  const { name, value } = e.target;
  setEditData({ ...editData, [name]: value });
};
 
const Save = (e) => {
  e.preventDefault();
  let filterData = tableData.filter((e) => e.id !== editData.id);
  let updatedData = [...filterData, { ...editData }].sort((a, b) =>
    a.id > b.id ? 1 : -1
  );
  setTableData(updatedData);
};

So what’s going on here, let me explain.

We will filter the updated state to check and delete any other copies available of it, and with the help of sorting save the updated state at the same position as it was before updating the row.

If we want to cancel the edit, we can just click on the Cancel button provided which will trigger the Cancel functionality.

  const Cancel = () => {
    setToggle(null);
  };

Working on Sorting the table

There is a method in javascript through which we can easily sort things out in their increasing and decreasing order.

As we have to deal with both numbers and strings, we will use the compare function in sort to compare between numbers to sort them as well.

const Sort = (sort) => {
  if (order === "asc") {
    const sorted = [...tableData.sort((a, b) => (a[sort] > b[sort] ? 1 : -1))];
    console.log(sorted);
    setTableData(sorted);
    setOrder("desc"); // descending order - desc
  } else if (order === "desc") {
    const sorted = [...tableData.sort((a, b) => (a[sort] < b[sort] ? 1 : -1))];
    console.log(sorted);
    setTableData(sorted);
    setOrder("asc");
  }
};

The initial value of the state used for sorting is ascending (“asc”) which will then be converted into descending order as shown above in the code.

The sorting will take place only when we click on the headings of each column. 

Working on Pagination for the table

For making the Pagination Component, there are some pre-defined values needed for specifying the index number of the first and last post for dividing the tableData accordingly.

{
  currentData.map((data) => {
    return (
      <>
        {toggle === data ? (
          <EditRow
            key={data.id}
            tableData={tableData}
            setTableData={setTableData}
            Cancel={Cancel}
            setEditData={setEditData}
            editData={editData}
          />
        ) : (
          <ReadOnly
            key={data.id}
            data={data}
            Edit={Edit}
            Delete={Delete}
          />
        )}
      </>
    );
  });
}
 

We want the app to increase and decrease the page numbers automatically depending upon the data and the the values like postPerPage we set like in this case there are 10 posts and we have sert the postPerPage to 4, so we will receive only 3 pages.

const Pagination = ({ number, setNumber, tableData, postPerPage }) => {
  const pageNumber = [];
// getting page numbers dynamically
  for (let i = 1; i <= Math.ceil(tableData.length / postPerPage); i++) {
    pageNumber.push(i);
  }
 
  const ChangePage = (pageNumber) => {
    setNumber(pageNumber);
  };
 
  const Prev = () => {
    if (number !== 1) {
      //to restrict going lower than page 1
      setNumber(number - 1);
    } else {
      setNumber(number);
    }
  };
 
  const Next = () => {
    if (number < 3) {
      //to restrict going above page 3 as it the last page of the app
      setNumber(number + 1);
    } else {
      alert("No further pages");
    }
  };
 
  return (
    <>
      <div className="my-3 text-center">
        <button
          className="px-3 py-1 mx-3 text-center btn-success"
          onClick={Prev}
        >
          Previous
        </button>
 
        {pageNumber.map((Elem) => {
          return (
            <>
              <button
                key={Elem.id}
                className="px-3 py-1 mx-3 text-center btn-outline-success"
                onClick={() => ChangePage(Elem)}
              >
                {Elem}
              </button>
            </>
          );
        })}
        <button
          className="px-3 py-1 mx-3 text-center btn-success"
          onClick={Next}
        >
          Next
        </button>
      </div>
    </>
  );
};
 

For a detailed explanation of the Pagination Component, click here.

Filter Component

We have come to the last section of our app, the filtration section. We will be filtering the table according to the username but you can use the same process to choose any other value or any number of values.

The UI part of the filter Component is very simple and consist of only one input field with a value attribute and an onChange event handler.

import React, { useState } from "react";
 
const Filter = ({search, setSearch}) => {
 
  const Change_Filter = (e) => {
    setSearch(e.target.value);
  };
  return (
    <>
      <div className="mb-4 mx-4">
        <input
          type="text"
          className="col-28pa from-control-sm"
          placeholder="Search....."
          value={search}
          onChange={Change_Filter}
        />
      </div>
    </>
  );
};
 
export default Filter;
 

Now lets understand how will the filtration take place. We will use the filter function before the map function with currentData. We did this because we want to access the data fetched dynamically before it even prints as UI of the app.

We have already defined a state specifically for this – search state. Now understand the following set of code

{
  currentData
    .filter((val) => {
      if (search === "") {
        return val;
      } else if (val.userName.toLowerCase().includes(search.toLowerCase())) {
        return val;
      }
    })
    .map((data) => {
      return (
        <>
          {toggle === data ? (
            <EditRow
              key={data.id}
              tableData={tableData}
              setTableData={setTableData}
              Cancel={Cancel}
              setEditData={setEditData}
              editData={editData}
            />
          ) : (
            <ReadOnly key={data.id} data={data} Edit={Edit} Delete={Delete} />
          )}
        </>
      );
    });
}
 

If the search state is empty, we will receive the data in the JSON format and it will be printed using the map method.

But if it contains a value, say username, then the row with that username will only be printed.

And now we can do any operation on that row after finding it like editing and deleting.

Conclusion

We have seen how to make a table in React which can perform operations like 

  1. Adding a New Row
  2. Deleting a Row
  3. Editing a Row
  4. Sorting the table for both alphabets and numerical values just by clicking the table header
  5. Pagination
  6. Username Filteration

We made all of this with only hooks without the use of any npm package or external library for any specific operation.

Share your love

Leave a Reply