DEV Community

Cover image for Server-side Paginated React Table
uguremirmustafa
uguremirmustafa

Posted on

Server-side Paginated React Table

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;
Enter fullscreen mode Exit fullscreen mode

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 });
  }
};
Enter fullscreen mode Exit fullscreen mode

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 }
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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!!

Top comments (3)

Collapse
 
jcancli profile image
Javier

Thank you! I thing you can make this change to improve the code

Current:
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]);

Updated:
const data = React.useMemo(() => {
return schools?.data.docs.map((i) => {
return {
col1: i.name,
col2: i.il,
col3: i.ilce,
col4: i.kont,
};
})
}
, [schools]);

Collapse
 
lorenso profile image
Lorenso D'Agostino

This helped me out soo much! 🙏

Collapse
 
uguremirmustafa profile image
uguremirmustafa

I am glad you find it helpful Lorenso 👍