Server-side Paginated React Table

Server-side Paginated React Table

Originally posted on my personal blog which is much more colorful and enyojable than hashnode. 🤭

Today we will create server-side paginated table with react-table. In order to see the full code you can visit my github repo.

The technologies used for this project:

  • NextJS
  • MongoDB
  • React-Table
  • React-Query
  • Mongoose and mongoose-paginated-v2 plugin

And this is the finished product:

final result of the code

Let's have a look at the Table component.

// components/Table.js

import React from 'react';
import { useTable, usePagination } from 'react-table';

function Table({ setPerPage, setPage, columns, data, currentpage, perPage, totalPage }) {
  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    prepareRow,
    page,
    pageOptions,
    state: { pageIndex, pageSize },
  } = useTable(
    {
      columns,
      data,
      useControlledState: (state) => {
        return React.useMemo(
          () => ({
            ...state,
            pageIndex: currentpage,
          }),
          [state, currentpage]
        );
      },
      initialState: { pageIndex: currentpage }, // Pass our hoisted table state
      manualPagination: true,
      pageCount: totalPage,
    },
    usePagination
  );

  return (
    <>
      <table {...getTableProps()} className="table-fixed">
        <thead>
          {headerGroups.map((headerGroup) => (
            <tr {...headerGroup.getHeaderGroupProps()}>
              {headerGroup.headers.slice(0, 1).map((column) => (
                <th column.getHeaderProps()}>
                  {column.render('Header')}
                </th>
              ))}
              {headerGroup.headers.slice(1).map((column) => (
                <th
                  {...column.getHeaderProps()}
                >
                  {column.render('Header')}
                </th>
              ))}
            </tr>
          ))}
        </thead>
        <tbody {...getTableBodyProps()}>
          {page.map((row, i) => {
            prepareRow(row);
            return (
              <tr {...row.getRowProps()}>
                {row.cells.map((cell) => {
                  return (
                    <td {...cell.getCellProps()}>
                      {cell.render('Cell')}
                    </td>
                  );
                })}
              </tr>
            );
          })}
        </tbody>
      </table>
    </>
  );
}

export default Table;

Here we render the table itself. But where the data is coming from? The parent component uses a custom react-query hook to fetch the data from api routes of NextJS.

// pages/api/schools/index.js

import School from '@models/School';
import dbConnect from '@utils/dbConnect';

dbConnect();

export default async function (req, res) {
  switch (req.method) {
    case 'GET':
      await getSchools(req, res);
      break;

    default:
      res.status(400).json({ success: false });
      break;
  }
}
const getSchools = async (req, res) => {
  try {
    let { page, perPage } = req.query;
    console.log(page, perPage);
    const options = {
      page: parseInt(page),
      limit: parseInt(perPage),
    };
    const schools = await School.paginate({}, options);
    res.status(200).json({
      success: true,
      data: schools,
    });
  } catch (error) {
    res.status(400).json({ success: false });
  }
};

And custom hook for fetching data from the API Routes:

// utils/useSchools

const { useQuery } = require('react-query');
const axios = require('axios');

export default function useSchools(page, perPage) {
  return useQuery(
    ['schools', page, perPage],
    async () => {
      const res = await axios.get(`/api/schools?perPage=${perPage}&page=${page}`);
      return res.data;
    },
    { keepPreviousData: true }
  );
}

The last important part is the index.js page where we call the custom hook.

import React, { useState } from 'react';
import Table from '@components/Table/Table';
import useSchools from '@utils/useSchools';

export default function Home() {
  const [page, setPage] = useState(1);
  const [perPage, setPerPage] = useState(10);
  const { data: schools, isLoading } = useSchools(page, perPage);
  const list = schools?.data.docs.map((i) => {
    return {
      col1: i.name,
      col2: i.il,
      col3: i.ilce,
      col4: i.kont,
    };
  });
  const data = React.useMemo(() => list, [schools]);
  const columns = React.useMemo(
    () => [
      {
        Header: 'okul adi',
        accessor: 'col1', // accessor is the "key" in the data
      },
      {
        Header: 'il',
        accessor: 'col2',
      },
      {
        Header: 'ilce',
        accessor: 'col3',
      },
      {
        Header: 'kontenjan',
        accessor: 'col4',
      },
    ],
    []
  );
  if (isLoading) return <div>loading...</div>;
  return (
    <div className="p-4 bg-white my-4 rounded shadow-xl grid">
      <Table
        data={data}
        columns={columns}
        setPage={setPage}
        setPerPage={setPerPage}
        currentpage={page}
        perPage={perPage}
        totalPage={schools?.data.totalPages}
      />
    </div>
  );
}

This is a bit long code to include all of it here. So you can check the rest of it from my github repo. Cheers!!