DEV Community

Cover image for Getting started with Tables using Next.js, Tanstack Table and Typescript
Francisco Luna πŸŒ™
Francisco Luna πŸŒ™

Posted on • Edited on

Getting started with Tables using Next.js, Tanstack Table and Typescript

Have you ever wondered how some admin panels handle their users in a efficient and comfortable way? Well, tables have come to the rescue!

They're an efficient way to display a huge amount of information with a proper user experience and design. However, before you'd need to work with libraries and dependencies such as Material UI table; and this is a good library, the problem is that it's about 300kb! You're delivering all that Javascript to your client which is not efficient.

Rubik cube. Image by Alexander Gray on Unsplash.

Times have changed and now we have alternatives such as Tanstack Table and Shadcn/UI. Amazing UI libraries that allow us to build intuitive and professional tables easily with Typescript support.

That's why in this guide you'll be learning how to build tables using Next.js, a modern fullstack framework based on React, Shadcn/UI, an amazing and modern UI library for React and JSONPlaceholder as our mockup data.

Let's get started!

Code. Image by Ferenc Almasi on Unsplash.

What We'll be Building

We're going to build a table to display users using Shadcn/UI, Next.js, Tanstack Table and JSONPlaceholder. You'll be able to work with real world data and tables after reading this guide with the mentioned technologies to deliver higher quality products and software.

Final Project

Project Requirements

To be able to understand and get the most out of this tutorial you need to have Node.js 18.17 or later installed on your computer, a solid understanding of React and Next.js and how to use third party libraries.

Installing Next.js and Shadcn/UI

We're going to install Next.js using the following command from the official website documentation:

npx create-next-app@latest

Use a name of your preference to name your project and since we're going to use Typescript and the App Router, we'll have the following setup:

Would you like to use TypeScript? Yes
Would you like to use ESLint? Yes
Would you like to use Tailwind CSS? Yes
Would you like to use src/ directory? Yes
Would you like to use App Router? (recommended) Yes
Would you like to customize the default import alias (@/*) Yes
What import alias would you like configured? @/*

To learn more about the installation process of Next.js, you can check of the official Next.js documentation

Now once the dependencies have been installed go to the project root folder and install Shadcn/UI:

npx shadcn-ui@latest init

You can select the settings you feel the most comfortable with to set up your components.json file. There will be your Shadcn/UI setup.

Learn more about the installation process of Shadcn/UI using Next.js in the official Shadcn/UI installation documentation with Next.js

Installing Shadcn/UI data table and Tanstack Table

Now that you've installed Shadcn/UI it's time to install the table component from the library. This component can be extended using Tanstack Table so we'll need to install that dependency as well.

Install the table component from Shadcn/UI using the following command:

npx shadcn-ui@latest add table

And install Tanstack Table, which uses React Table under the hood and will allow you to add filtering and pagination in the future:

npm install @tanstack/react-table

See the official TanStack Table Documentation

Now we can start by fetching data and creating the Typescript types.

Getting the data

In a real world application you're going to use actual API endpoints with actual client data. However, in this tutorial we'll be using JSONPlaceholder.

And what is this service? According to the official website:

JSONPlaceholder is a free online REST API that you can use whenever you need some fake data.

In this case, we'll be fetching fictional user data using the following endpoint:

https://jsonplaceholder.typicode.com/users

Response from JSONPlaceholder users endpoint

As you can see, the data we're getting is huge and you might be tempted to create the types yourself. I wouldn't recommend you to do so, but instead, using a website like Transform Tools. This is a website that'll allow us to convert files to other formats such as JSON to Typescript Types or React Props.

Copy the response data you're getting from the API and paste it here: https://transform.tools/json-to-typescript

Create a new file in your src file called types.ts and paste the interface from the tool you've used before. You're going to save the types of your project there since this is a small project. Keep in mind you'll need to create and organize different folders for types as your project scales.

Now after some tweaks in the TS interfaces, your data could look like this:



// src/types.ts
export interface User {
  id: number
  name: string
  username: string
  email: string
  address: Address
  phone: string
  website: string
  company: Company
}

export interface Address {
  street: string
  suite: string
  city: string
  zipcode: string
  geo: Geo
}

export interface Geo {
  lat: string
  lng: string
}

export interface Company {
  name: string
  catchPhrase: string
  bs: string
}


Enter fullscreen mode Exit fullscreen mode

Fetching Data and Creating the Services

Let's get started by creating a new folder in our src folder called services. In this folder we're going to create our async functions responsible for fetching data.

Create a new file called index.ts, since we'll only have a single file here for now. Remember to add the return types explicitly as well.



// src/services/index.ts
import { User } from "@/types";

const getUsers = async (): Promise<User[]> => {
  const data = await fetch("https://jsonplaceholder.typicode.com/users");

  return data.json();
};

export default getUsers;



Enter fullscreen mode Exit fullscreen mode

Columns and How to Use them

Once you've created the services, you can define the columns of your table. Columns are where you define the core of what your table will look like. They define the data that will be displayed, how it will be formatted, sorted and filtered.

Create a new file called columns.tsx. We're going to display the following fields from the user:

  • Username
  • Email
  • Phone Number
  • Name

Hence we can create the columns as the following:



"use client"

import { ColumnDef } from "@tanstack/react-table"
import { User } from "@/types"

export const columns: ColumnDef<User>[] = [
  {
    accessorKey: "username",
    header: "Username",
  },
  {
    accessorKey: "email",
    header: "Email",
  },
  {
    accessorKey: "phone",
    header: "Phone Number",
  },
  {
    accessorKey: "name",
    header: "Name"
  }
]


Enter fullscreen mode Exit fullscreen mode

What's happening in this code? First, we're creating the column definitions importing the ColumnDef type definition from Tanstack Table. This is a generic type, so we add the type we want to infer, in this case it's User from our types.

We create an array with the object definitions we want to display in our table. Each object definition has the properties accessorKey and header. accessorKey refers to the core object field we'll be using, and header refers to the property's display header in the table.

Learn more about the columns definitions in the official Shadncn/UI documentation

Data Tables and their Purpose

Now it's time to create a reusable <DataTable /> component to render our table.

Go to the folder called ui inside our components folder in src. Create a new file called data-table.tsx.

You're going to use the following code from the official Shadcn/UI documentation:



// src/components/ui/data-table.tsx
"use client"

import {
  ColumnDef,
  flexRender,
  getCoreRowModel,
  useReactTable,
} from "@tanstack/react-table"

import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table"

interface DataTableProps<TData, TValue> {
  columns: ColumnDef<TData, TValue>[]
  data: TData[]
}

export function DataTable<TData, TValue>({
  columns,
  data,
}: DataTableProps<TData, TValue>) {
  const table = useReactTable({
    data,
    columns,
    getCoreRowModel: getCoreRowModel(),
  })

  return (
    <div className="rounded-md border">
      <Table>
        <TableHeader>
          {table.getHeaderGroups().map((headerGroup) => (
            <TableRow key={headerGroup.id}>
              {headerGroup.headers.map((header) => {
                return (
                  <TableHead key={header.id}>
                    {header.isPlaceholder
                      ? null
                      : flexRender(
                          header.column.columnDef.header,
                          header.getContext()
                        )}
                  </TableHead>
                )
              })}
            </TableRow>
          ))}
        </TableHeader>
        <TableBody>
          {table.getRowModel().rows?.length ? (
            table.getRowModel().rows.map((row) => (
              <TableRow
                key={row.id}
                data-state={row.getIsSelected() && "selected"}
              >
                {row.getVisibleCells().map((cell) => (
                  <TableCell key={cell.id}>
                    {flexRender(cell.column.columnDef.cell, cell.getContext())}
                  </TableCell>
                ))}
              </TableRow>
            ))
          ) : (
            <TableRow>
              <TableCell colSpan={columns.length} className="h-24 text-center">
                No results.
              </TableCell>
            </TableRow>
          )}
        </TableBody>
      </Table>
    </div>
  )
}


Enter fullscreen mode Exit fullscreen mode

Let's see how this code works:

  1. Imports: The component imports necessary functions and components from @tanstack/react-table and our table component installed previously @/components/ui/table.

  2. Props: The DataTable component receives two props: columns and data. columns represents an array of column definitions, and data represents the data to be displayed in the table.

  3. useReactTable Hook: Inside the component, the useReactTable hook is used to create a table instance. It takes data, columns, and getCoreRowModelas parameters.

  4. Rendering Header: The component renders the table header by mapping over the header groups obtained from the table instance. For each header group, it maps over the headers and renders a TableHead component for each header.

  5. Rendering Body: The component renders the table body by mapping over the rows obtained from the table instance. For each row, it renders a TableRow component, setting the data-state attribute based on whether the row is selected. Within each row it maps over the visible cells and renders a TableCell component for each cell.

  6. FlexRender: The flexRender function is used to conditionally render the content of each header and cell based on the provided context.

  7. No Results Message: If there are no rows to display, the component renders a single TableRow with a TableCell spanning all columns, displaying a "No results" message.

Displaying the Table in our First Page

Let's use what we've built so far to set up our table in src/app/page.tsx with the following code:



import { columns } from "@/app/columns";
import { DataTable } from "@/components/ui/data-table";
import getUsers from "@/services";

export default async function Home() {

  const data = await getUsers();

  return (
    <main className="flex min-h-screen flex-col items-center justify-between p-24">
      <DataTable columns={columns} data={data} />
    </main>
  );
}


Enter fullscreen mode Exit fullscreen mode

This code works the following way:

  1. Imports: The code imports the columns definition from the @/app/columns file and the DataTable component from the @/components/ui/data-table file. Additionally, it imports the getUsers function from @/services.

  2. Fetching Data: Inside the Home function, the getUsers function is called asynchronously to fetch user data. This function makes an HTTP request to an API endpoint to retrieve the data.

  3. Rendering: Once the data is fetched, it is passed as props to the DataTable component along with the columns definition. The DataTable component renders the table UI based on the provided data and column definitions.

  4. Server side rendering: Next.js SSR is used. Server components allow fetching data directly within the component, enabling server-side rendering of data. In this case, getUsers is invoked directly within the component to fetch user data.

Final Result

If you've followed the steps properly, your final result should look like this:

Final Table

Conclusion

Congratulations to reaching this point! You've learned how to create professional tables using Tanstack Table, Next.js and ShadcnUI. You've also learned how to connect to the JSONPlaceholder API and how to consume an API endpoint.

You can also implement pagination, filtering or sorting in the future to keep improving your skills with tables in web development. I'll create more advanced guides in the future about this topic.

Thank you so much for reading this tutorial and I hope you've learned something new!

Top comments (4)

Collapse
 
lemi_melkamu_5831c2579d96 profile image
lemi melkamu

amazing walk through thank you, we look forward to more advanced features like sorting,filtering and searching with tanstack table thank you

Collapse
 
franciscolunadev82 profile image
Francisco Luna πŸŒ™

It's a pleasure to help my friend! I'll keep those topics in mind to explain them in the future. Thank you so much for supporting my content as well.

Collapse
 
dunavista profile image
dunavista

I am quite curious to know how you got this to work, when there is stuff missing..........

Collapse
 
franciscolunadev82 profile image
Francisco Luna πŸŒ™

Oh, my apologies if something isn't working properly. What part of the guide are you having problems with? I'll update the blog post as needed.