Black Box Solutions

Creating software solutions one box at a time.

Building data grids from scratch is a lot of work, but thankfully nowadays, we have help. When used right, Artificial Intelligence can be used to aid in everyday tasks to help get us started. It may not be the end result right off the back and will take some practice working with your new co-worker.

For this sing-along, we will use Create React App to get us started. So let us begin by running the command to create our React project.

npx create-react-app fun-with-datagrid

Once the terminal has finished building the project, we can move into the directory and open it up.

cd fun-with-datagrid

Of course, by this point, we can run “npm start” which will give us the initial basic “Create React App” application, but that’s not what we want, so let’s clean up.

  1. Remove files, [“App.css”, “logo.svg”] from the src directory.
  2. Add a directory “components” under the “src” directory.
  3. Add a directory under “components” called “DataGrid”
  4. Add 2 files to the “DataGrid” directory
    • DataGrid.jsx
    • DataGrid.css

Now let’s ask a friend ChatGPT to create a DataGrid component with pagination. The question I typed in went as follows:

Can you please create a data grid component in React that has pagination

The question produced two files from ChatGPT.

DataGrid.jsx File

import React, { useState } from "react";

const DataGrid = ({ data, pageSize }) => {
  const [currentPage, setCurrentPage] = useState(1);

  const totalPages = Math.ceil(data.length / pageSize);
  const startIndex = (currentPage - 1) * pageSize;
  const endIndex = startIndex + pageSize;
  const currentData = data.slice(startIndex, endIndex);

  const handlePageChange = (pageNumber) => {
    setCurrentPage(pageNumber);
  };

  return (
    <div>
      <table>
        {/* Render your table headers here */}
        <thead>
          <tr>
            <th>ID</th>
            <th>Name</th>
            {/* Add more columns as needed */}
          </tr>
        </thead>
        <tbody>
          {/* Render table rows with data */}
          {currentData.map((item) => (
            <tr key={item.id}>
              <td>{item.id}</td>
              <td>{item.name}</td>
              {/* Render additional columns */}
            </tr>
          ))}
        </tbody>
      </table>

      {/* Pagination */}
      <div>
        {/* Render pagination controls */}
        {Array.from({ length: totalPages }, (_, index) => index + 1).map(
          (pageNumber) => (
            <button
              key={pageNumber}
              onClick={() => handlePageChange(pageNumber)}
              disabled={pageNumber === currentPage}
            >
              {pageNumber}
            </button>
          )
        )}
      </div>
    </div>
  );
};

export default DataGrid;

App.js File

import React from "react";
import DataGrid from "./components/DataGrid/DataGrid";

const App = () => {
  const data = [
    { id: 1, name: "John Doe" },
    { id: 2, name: "Jane Smith" },
    // Add more data items as needed
  ];

  return (
    <div>
      <h1>Data Grid Example</h1>
      <DataGrid data={data} pageSize={5} />
    </div>
  );
};

export default App;

Now run the command “npm start” and see the following output.

The output is a good start; however, there is a flaw here, and that is it’s not reusable. Well, I guess you could reuse the component if you present the same data objects in different locations, but we want a more reusable DataGrid that we can pass in any data format. So we will go back to ChatGPT and ask:

Make the headers and column dynamic

And let’s go ahead and ask to style the DataGrid, so it is not so bare:

Can you please add style the datagrid

As a side note, ChatGPT did not specify how to add styles to the “DataGrid.jsx” file. We will need to add the import to the top of the file so the final result will look like: 

import React, { useState } from "react";
import "./DataGrid.css";

const DataGrid = ({ data, pageSize }) => {
  const [currentPage, setCurrentPage] = useState(1);

  const totalPages = Math.ceil(data.length / pageSize);
  const startIndex = (currentPage - 1) * pageSize;
  const endIndex = startIndex + pageSize;
  const currentData = data.slice(startIndex, endIndex);

  const headers = Object.keys(data[0] || {});

  const handlePageChange = (pageNumber) => {
    setCurrentPage(pageNumber);
  };

  return (
    <div>
      <table className="data-grid-table">
        <thead>
          <tr>
            {headers.map((header) => (
              <th key={header}>{header}</th>
            ))}
          </tr>
        </thead>
        <tbody>
          {currentData.map((item, index) => (
            <tr key={index}>
              {headers.map((header) => (
                <td key={header}>{item[header]}</td>
              ))}
            </tr>
          ))}
        </tbody>
      </table>

      <div className="pagination">
        {Array.from({ length: totalPages }, (_, index) => index + 1).map(
          (pageNumber) => (
            <button
              key={pageNumber}
              onClick={() => handlePageChange(pageNumber)}
              className={pageNumber === currentPage ? "active" : ""}
            >
              {pageNumber}
            </button>
          )
        )}
      </div>
    </div>
  );
};

export default DataGrid;

Update the DataGrid.css file.

.data-grid-table {
  width: 100%;
  border-collapse: collapse;
  margin-bottom: 20px;
}

.data-grid-table th,
.data-grid-table td {
  padding: 8px;
  border: 1px solid #ccc;
}

.data-grid-table th {
  background-color: #f2f2f2;
}

.data-grid-table td {
  text-align: center;
}

.data-grid-table td:first-child {
  font-weight: bold;
}

.data-grid-table tr:hover {
  background-color: #f9f9f9;
}

.pagination {
  display: flex;
  justify-content: center;
  margin-top: 20px;
}

.pagination button {
  padding: 8px 12px;
  border: none;
  background-color: #e0e0e0;
  color: #333;
  margin-right: 5px;
  cursor: pointer;
}

.pagination button:hover {
  background-color: #ccc;
}

.pagination button.active {
  background-color: #333;
  color: #fff;
}

Let us add a little margin to the body of the application by altering the margin in index.css file.

body {
  margin: 50px; /* Added to make the example look better */
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
    "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
    monospace;
}

Let’s see what this looks like now.

Okay, not too shabby, but let’s think a moment about the small dataset. What would this look like with hundred rows or a thousand?🤔. To simulate a large data set with some fake data, we will have to bring in an NPM library by the name of “@faker-js/faker”. This library will allow us to generate a massive amount of fake data to pump into our DataGrid, and the setup is pretty straightforward.

  1. First make sure to stop the node from running then install the “faker-js” package with “npm install @faker-js/faker --save-dev”.
  2. Add a “data” directory under the “src” directory.
  3. Create a file called “mock-data.js” in the “data” directory with the content below. This function will generate a thousand records.
import { faker } from "@faker-js/faker";

export default function createData() {
  let data = [];
  for (let i = 0; i < 1000; i++) {
    data.push({
      employeeId: i,
      name: faker.person.fullName(),
      email: faker.internet.email(),
      business: faker.company.name(),
    });
  }
  return data;
}

4. Update “App.js” to import the createData function and replace the variable “const data” to call our function. The new file should look like this:

import React from "react";
import DataGrid from "./components/DataGrid";
import createData from "./data/mock-data";

const App = () => {
  const data = createData();

  return (
    <div>
      <h1>Data Grid Example</h1>
      <DataGrid data={data} pageSize={5} />
    </div>
  );
};

export default App;

After we run “npm start” we should have output like the image below.

Well, that’s not good. We need a different type of solution that can display something like “1 2 3 4 … X” and “1 … 10, 11, 12, 13, … X” with X being the last page. Here is where the rubber meets the road, and we need to challenge our minds to create an algorithm to generate such an output. I’m not saying it is impossible to solve it with the A.I. ChatGPT, BUT how much going back and forth would we have to do? How long would that take?

Thinking in Algorithms

It’s time to introduce a black box function and declare its inputs and output. We already know what we want the result to be but to be more precise, we want to return an array of strings like “[‘1’, ’2’, ‘3’, ‘4’, ‘5’, ‘…’, ‘100’]” and be fluid enough also to return “[‘1’, ’…’, ‘10’, ‘11’, ‘12’, ‘13’, ‘…’, ‘100’]”.

Now to declare what parameters need to pass in, we will work backward from the return result. First, we need to know our current page to find out what numbers to display for the middle group of numbers. Next, we need to know how many pages to show, so we need the number of data items to get that number. Lastly, is the number of items we want to display per page.

The function specs would be

Input:

  1. Current Page
  2. Number of Items
  3. Number Per Page

Output: Return a string array.

Once we determined our inputs and outputs we can create a shell of a function inside the “DataGrid.jsx” file at the bottom o the file.

function pagination(currentPage, items, numberPerPage) {
    return [];
}

Start writing out the requirements to reach our end goal.

function pagination(currentPage, items, numberPerPage) {
    // initiate variables

    // if there is only one page, return an empty array

    // calculate the number of pages

    // if there is only enough items for 10 pages or less, display all pages

    // current page is less than 4 display [1, 2, 3, 4, 5, ..., X] 
    // where X is the last page

    // current page is greater than the last page - 3 display 
    // [1, ..., X-4, X-3, X-2, X-1, X] where X is the last page

    // default view is to display [1, ..., C-2, C-1, C, C+1, ..., X] 
    // where X is the last page and C is the current page
    
    // return the page display
}

Once the code is inserted into the pagination function, we can go ahead and implement it inside the DataGrid component like so:

const pageDisplay = pagination(currentPage, data.length, pageSize);

Then replace the pagination with our new function return and make sure to address the (…) scenario from being clickable.

FROM

  <div className="pagination">
        {Array.from({ length: totalPages }, (_, index) => index + 1).map(
          (pageNumber) => (
            <button
              key={pageNumber}
              onClick={() => handlePageChange(pageNumber)}
              className={pageNumber === currentPage ? "active" : ""}
            >
              {pageNumber}
            </button>
          )
        )}
      </div>

TO

      <div className="pagination">
        {pageDisplay.map((item, index) => {
          if (item === "...") {
            return <span key={index}>...</span>;
          }
          return (
            <button
              key={index}
              onClick={() => handlePageChange(item)}
              className={item === currentPage ? "active" : ""}
            >
              {item}
            </button>
          );
        })}
      </div>

Conclusion

All in all, working with ChatGPT does make things easier to get started. It may be easy for a person to state that ChatGPT can do everything I want; however, working with clients is not that simple. Clients want a custom look and want to be able to use an application how they see fit. When those occasions arise, then solely relying on an A.I. will leave you spinning in circles trying to ask the right questions. If you would like the source code please visit my GitHub GetBlackBoxSolutions/fun-with-datagrid to get source code to this.

Well, I hope this article helps, and as always, this has been another Black Box Solution!

Leave a Reply

Your email address will not be published. Required fields are marked *

Dennis Garcia

Full Stack Engineer with expertise in building system applications in SQL, .NET Core, and React

Latest Posts



Categories


Tags