Building a Reusable Loadable Table in ReactJS

Welcome to our guide on building a reusable loadable table component in ReactJS. By the end of this article, you'll have a versatile table component that you can easily integrate into your React or Next.js projects. This component will have features like a loading state, dynamic header, empty state, and various customizable props and states. The best part? You can easily use this component anywhere in your app. We'll also keep things simple by using pure HTML for the table structure.

Introduction

Tables are a fundamental part of many web applications, and creating reusable components can greatly simplify development and maintenance. Our goal here is to create a LoadableTable component that you can drop into any part of your app. We'll be using pure HTML for the table structure, making it lightweight and easy to style to match your application's design.

Prerequisites

Before we dive into creating our reusable table component, make sure you have the following prerequisites in place:

  • Basic knowledge of ReactJS or Next.js

  • Familiarity with TypeScript and TailwindCSS

  • An existing React or Next.js project

If you meet these requirements, you're ready to get started!

Getting Started

Let's jump right in and start creating our LoadableTable component. First, navigate to your project's components folder and create a new file named LoadableTable.tsx. Inside this file, add the following code:

// Import necessary dependencies
import SpinnerIcon from '@/elements/SpinnerIcon';
import {
  Children,
  cloneElement,
  ReactNode,
  useRef,
  useEffect,
  useState,
  ReactElement,
  FC,
} from 'react';

// Define the props for the LoadableTable component
interface LoadableTableProps {
  isLoading: boolean;
  noData: boolean;
  header: ReactElement | boolean;
  noDataText: string | ReactElement;
  children: ReactNode;
}

// Create the LoadableTable component
const LoadableTable: FC<LoadableTableProps> = ({
  isLoading,
  children,
  header,
  noData,
  noDataText = 'No Data found',
}) => {
  // Initialize necessary references and state
  const childrenRef = useRef<HTMLTableSectionElement[]>([]);
  const [columnCount, setColumnCount] = useState(0);

  useEffect(() => {
    // Count the number of columns in the table header
    const count = childrenRef.current[0]?.querySelectorAll('th').length;

    setColumnCount(count);
  }, []);

  // Render the table component
  return (
    <table className="items-center w-full bg-transparent border-collapse">
      <thead>
        {header &&
          Children.map(header, (child, index) =>
            cloneElement(child as ReactElement, {
              ref: (ref: HTMLTableSectionElement) =>
                (childrenRef.current[index] = ref),
            })
          )}
      </thead>

      {isLoading && (
        <tbody>
          <tr className="single-item">
            <td
              className="table-data"
              align="center"
              colSpan={columnCount}
              style={{
                padding: '70px 0',
                border: 0,
              }}
            >
              <SpinnerIcon />
            </td>
          </tr>
        </tbody>
      )}

      {!isLoading && !noData && children}

      {!isLoading &&
        noData &&
        (typeof noDataText === 'string' ? (
          <tbody>
            <tr className="single-item">
              <td
                align="center"
                colSpan={columnCount}
                style={{
                  padding: '60px 0',
                  border: 0,
                }}
              >
                <div style={{ textAlign: 'center', fontSize: '1rem' }}>
                  {noDataText}
                </div>
              </td>
            </tr>
          </tbody>
        ) : (
          <tbody>
            <tr className="single-item">
              <td
                align="center"
                colSpan={columnCount}
                style={{
                  padding: '40px 0',
                  border: 0,
                }}
              >
                {noDataText}
              </td>
            </tr>
          </tbody>
        ))}
    </table>
  );
};

export default LoadableTable;

In the code above, we've created the LoadableTable component with props for loading state, header, no data state, and more. We've also included logic to dynamically calculate the number of columns in the table header. We've imported a SpinnerIcon from our elements folder, which will display a spinning SVG to indicate the table's loading state. You can use the provided SpinnerIcon code in your project.

const SpinnerIcon = () => {
  return (
    <svg
      width={40}
      height={40}
      viewBox="0 0 38 38"
      xmlns="http://www.w3.org/2000/svg"
    >
      <defs>
        <linearGradient x1="8.042%" y1="0%" x2="65.682%" y2="23.865%" id="a">
          <stop
            stopColor={'#38B776'}
            stopOpacity="0"
            offset="0%"
          ></stop>
          <stop
            stopColor={'#38B776'}
            stopOpacity=".631"
            offset="63.146%"
          ></stop>
          <stop stopColor={'#38B776'} offset="100%"></stop>
        </linearGradient>
      </defs>
      <g fill="none" fillRule="evenodd">
        <g transform="translate(1 1)">
          <path
            d="M36 18c0-9.94-8.06-18-18-18"
            id="Oval-2"
            stroke={'#38B776'}
            strokeWidth="2"
          >
            <animateTransform
              attributeName="transform"
              type="rotate"
              from="0 18 18"
              to="360 18 18"
              dur="0.9s"
              repeatCount="indefinite"
            ></animateTransform>
          </path>
          <circle fill={'#38B776'} cx="36" cy="18" r="1">
            <animateTransform
              attributeName="transform"
              type="rotate"
              from="0 18 18"
              to="360 18 18"
              dur="0.9s"
              repeatCount="indefinite"
            ></animateTransform>
          </circle>
        </g>
      </g>
    </svg>
  );
}

export default SpinnerIcon;

Using Our Reusable Loadable Table Component

Now that we have our LoadableTable component, let's put it to use. Create another file called UsersTable.tsx and use the LoadableTable component to display user data. Here's the code for UsersTable:

import LoadableTable from '@/components/LoadableTable';

const UsersTable = ({ isLoading, users }: { isLoading: boolean; users: [] }) => {
  return (
    <LoadableTable
      header={
        <tr>
          <th className="table-head">User ID</th>
          <th className="table-head">User Name</th>
          <th className="table-head">Email</th>
          <th className="table-head">Date Registered</th>
        </tr>
      }
      isLoading={isLoading}
      noData={users?.length === 0}
    >
      <tbody>
        {users &&
          users.map((user) => (
            <tr key={user._id}>
              <td className="table-data">{user.userId}</td>
              <td className="table-data">{user.name}</td>
              <td className="table-data">{user.email}</td>
              <td className="table-data">{user.created_at}</td>
            </tr>
          ))}
      </tbody>
    </LoadableTable>
  );
};

export default UsersTable;

In this code, we've created a UsersTable component that uses the LoadableTable component to display user data. We've provided a dynamic header and checked for loading and empty data states. Feel free to use this template with your own data to populate the table. You can also include other props like footer to pass a pagination component, etc.

That's it! You can now use this LoadableTable component anywhere in your app.

Conclusion

Reusable components like our LoadableTable can save you time and make your project more maintainable. We hope you found this guide insightful and that it helps you create efficient and flexible tables for your React or Next.js projects. Thank you for reading!

16
Subscribe to my newsletter

Read articles from Emerenini Cynthia Ngozi directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Emerenini Cynthia Ngozi
Emerenini Cynthia Ngozi

Frontend Software Engineer with 3+ years of experience in solving software problems