Getting started with Tables using Next.js, Tanstack Table and Typescript
Tags: webdev, beginners, learning, typescript
Published: 2024-05-17 01:58:11

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.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w85sbc1ikp7geqkth105.jpg)


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.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z9g5pvcqg4z9fepo22iu.jpg)


## 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bedf2ht4972ubd8bi1un.png)

## 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](https://nextjs.org/docs/getting-started/installation)

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](https://ui.shadcn.com/docs/installation/next)

## 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](https://tanstack.com/table/latest/docs/introduction/docs/introduction)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qafmp1mjlqkq7kaaunon.png)


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:

```ts
// 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
}
```

## 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. 

```ts
// 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;

```

## 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:

```ts
"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"
  }
]
```
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](https://ui.shadcn.com/docs/components/data-table)

## 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:

```ts
// 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>
  )
}
```

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 `getCoreRowModel`as 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:

```ts
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>
  );
}
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/arkl5jwvx3mxn3jq60xj.png)

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