Customize and Style Complex Data in React Table
Tags: react, javascript, typescript, nextjs
Published: 2022-04-05 13:31:26

---
title: Customize and Style Complex Data in React Table
published: true
date: 2022-04-04 00:00:00 UTC
tags: react,javascript,typescript,nextjs
canonical_url: https://www.paigeniedringhaus.com/blog/customize-and-style-complex-data-in-react-table
---

[![Spreadsheet of data on a laptop screen](https://www.paigeniedringhaus.com/static/948eca5c77224beba7888952fdb490b7/15ec7/spreadsheet.jpg "Spreadsheet of data on a laptop screen")](/static/948eca5c77224beba7888952fdb490b7/861b7/spreadsheet.jpg)

## Introduction

This past summer, I started working for an Internet of Things (IoT) startup, **[Blues Wireless](https://blues.io/?&utm_source=dev.to&utm_medium=web&utm_campaign=niedringhaus-effect&utm_content=ep-2)**, that aims to make IoT development simpler - even when reliable Internet connections are not available. Blues does this via **[Notecards](https://blues.io/products/notecard/?&utm_source=dev.t&utm_medium=web&utm_campaign=niedringhaus-effect&utm_content=ep-2)** - prepaid cellular devices that can be embedded into any IoT device "on the edge" to transmit sensor data as JSON to a secure cloud: **[Notehub](https://notehub.io/?&utm_source=dev.t&utm_medium=web&utm_campaign=niedringhaus-effect&utm_content=ep-2)**.

Frontend web development is what I specialize in, not hardware or firmware development, so as I get more familiar with IoT development, I started by building a simpler project: an **[asset tracker](https://www.hackster.io/paige-niedringhaus/low-code-gps-asset-tracker-and-map-display-b10419)** using a Blues **[Notecard](https://shop.blues.io/products/note-nbgl-500/?&utm_source=dev.t&utm_medium=web&utm_campaign=niedringhaus-effect&utm_content=ep-2)**, Blues **[Notecarrier AL](https://shop.blues.io/products/carr-al/?&utm_source=dev.t&utm_medium=web&utm_campaign=niedringhaus-effect&utm_content=ep-2)** with a built-in GPS antenna, and a small **[lithium-ion polymer (LiPo) battery](https://www.adafruit.com/product/328)** to power the device.

With the help of the Blues **[developer experience docs](https://dev.blues.io/guides-and-tutorials/notecard-guides/asset-tracking/?&utm_source=dev.t&utm_medium=web&utm_campaign=niedringhaus-effect&utm_content=ep-2)**, I had temperature, voltage, and GPS location data being delivered to the Notehub cloud from my tracker in less than half an hour. A good start, but the way that data from sensors really becomes useful is when it's displayed to users in some sort of UI. It could be maps, charts, or in my case, tables.

So I wanted to take my data from the Notehub cloud and pump it into a custom-made dashboard to track and display the Notecard's location and data readings from the real world. As a frontend developer, React is my current JavaScript framework of choice, and to get some more Next.js experience, I decided to build a Next.js Typescript-powered dashboard, and I learned a ton of interesting things in the process. Over the course of a series of blog posts (which I'll link to in this article), I'll share with you some of the cools things I discovered.

> If you missed the first two installments of my blogs about building an asset tracking map in Next.js using React Leaflet, you can read it **[here](https://www.paigeniedringhaus.com/blog/create-an-asset-tracker-with-next-js-and-react-leaflet)**.
> 
> The second one about making data visualization charts is available **[here](.https://www.paigeniedringhaus.com/blog/build-and-custom-style-recharts-data-charts)**.

**This post will show you how to use the React Table library to make a data table and customize the data displayed in the cells, filled with real-world location data.**

Here's what the final dashboard looks like - the table of location coordinates and data at the bottom is the focus for this particular post.

{% youtube uUJiX9q8x9M %} 

---
 
## Create a table component in the Next.js app

**Please note:** This article will not go through the initial setup of a brand new Next.js app - that's outside the scope of this blog. If you're starting from scratch, I would recommend following the [Next.js starter app with Typescript documentation](https://nextjs.org/docs/basic-features/typescript).

> If you'd prefer, you can also fork and download my whole, working code base from the GitHub repo **[here](https://github.com/paigen11/react-gps-asset-tracker-dashboard)**.

### Install table dependencies

Let's kick off this post by adding the necessary table library to this Next.js app.

Although there's many React-focused table libraries and components to choose from, I went with the **[React Table library](https://react-table.tanstack.com/)** because it is a "headless" table-library that gives you a collection of lightweight, composable, and extensible hooks for building powerful tools and data grid experiences.

The "headless" part means that there's no markup or styles rendered via React Table, so you can customize and style it exactly as you want. It may sound a little odd at first, but it actually ends up working out really well as you'll see soon.

So, install React Table in the Next.js app by typing this command into the terminal.

```shell
$ npm install react-table 
```

> **Note:** React Table is compatible with React v16.8+, so please ensure your app's version of React is at least to here if not above.

**Typescript Note:**

If you're using Typescript in your project (like I am), you'll also want to install the following dev dependency to avoid Typescript errors:

```shell
$ npm install @types/react-table --save-dev
```

And now, we're ready to start using React Table to make a table in the application to display location data.

### Create the `<EventTable>` component and style it

[![Table showing Notecard location, timestamp, and nearest cell tower at the time.](https://www.paigeniedringhaus.com/static/f8919d22615460f201fc73fbdf4d3c1c/1e043/table-hero.png "Table showing Notecard location, timestamp, and nearest cell tower at the time.")](/static/f8919d22615460f201fc73fbdf4d3c1c/aaf7f/table-hero.png)

_The custom styled event table we'll be building._

For my table, I want to display **"events"** , which are what Blues Wireless's Notecards produce and send to its cloud Notehub. Each event is a separate JSON object, and although there are all sorts of different event types stored in a single project, the ones of concern today are the **`_track.qo`** events.

**`_track.qo` event example**

A typical `_track.qo` event looks like this:

```json
{
    "uid": "d7cf7475-45ff-4d8c-b02a-64de9f15f538",
    "device_uid": "dev:864475ABCDEF",
    "file": "_track.qo",
    "captured": "2021-11-05T16:10:52Z",
    "received": "2021-11-05T16:11:29Z",
    "body": {
      "hdop": 3,
      "seconds": 90,
      "motion": 76,
      "temperature": 20.1875,
      "time": 1636123230,
      "voltage": 4.2578125
    },
    "gps_location": {
      "when": "2021-11-05T16:10:53Z",
      "name": "Sandy Springs, GA",
      "country": "US",
      "timezone": "America/New_York",
      "latitude": 33.913747500000014,
      "longitude": -84.35008984375
    }
  }
```

It contains data like `temperature`, `time`, `voltage`, and `gps_location` coordinates. Useful stuff you might want to display in a table for easy readability, right?

Right. So here's how I built a reusable table component in a new file in the Next.js app named `EventTable.tsx`.

I recommend copying the following code and pasting it into your own component file, and you can also click on the file title below to see the live code repo in GitHub.

**[`EventTable`](https://github.com/paigen11/react-gps-asset-tracker-dashboard/blob/main/src/components/EventTable.tsx)**

```typescript
/* eslint-disable react/jsx-key */
import { usePagination, useTable, Column } from "react-table";
import styles from "../../styles/EventTable.module.scss";

const EventTable = ({
  columns,
  data,
}: {
  data: Array<any>;
  columns: Array<Column>;
}) => {
  const {
    getTableProps, // table props from react-table
    getTableBodyProps, // table body props from react-table
    headerGroups, // headerGroups, if your table has groupings
    prepareRow, // rows for the table based on the data passed
    page, // Instead of using 'rows', we'll use page
    canPreviousPage,
    canNextPage,
    pageOptions,
    pageCount,
    gotoPage,
    nextPage,
    previousPage,
    setPageSize,
    state: { pageIndex, pageSize },
  } = useTable(
    {
      columns,
      data,
    },
    usePagination
  );

  return (
    <>
      <h2>Tracker Events</h2>
      <table className={styles.tableWrapper} {...getTableProps()}>
        <thead>
          {headerGroups.map((headerGroup) => (
            <tr {...headerGroup.getHeaderGroupProps()}>
              {headerGroup.headers.map((column, _index) => (
                <th {...column.getHeaderProps()}>{column.render("Header")}</th>
              ))}
            </tr>
          ))}
        </thead>
        <tbody {...getTableBodyProps()}>
          {page.map((row) => {
            prepareRow(row);
            return (
              <tr {...row.getRowProps()}>
                {row.cells.map((cell) => {
                  return (
                    <td {...cell.getCellProps()}>{cell.render("Cell")}</td>
                  );
                })}
              </tr>
            );
          })}
        </tbody>
      </table>
      <div className="pagination">
        <button onClick={() => gotoPage(0)} disabled={!canPreviousPage}>
          {"<<"}
        </button>{" "}
        <button onClick={() => previousPage()} disabled={!canPreviousPage}>
          {"<"}
        </button>{" "}
        <button onClick={() => nextPage()} disabled={!canNextPage}>
          {">"}
        </button>{" "}
        <button onClick={() => gotoPage(pageCount - 1)} disabled={!canNextPage}>
          {">>"}
        </button>{" "}
        <span>
          Page{" "}
          <strong>
            {pageIndex + 1} of {pageOptions.length}
          </strong>{" "}
        </span>
        <span>
          | Go to page:{" "}
          <input
            type="number"
            defaultValue={pageIndex + 1}
            onChange={(e) => {
              const page = e.target.value ? Number(e.target.value) - 1 : 0;
              gotoPage(page);
            }}
            style={{ width: "100px" }}
          />
        </span>{" "}
        <select
          value={pageSize}
          onChange={(e) => {
            setPageSize(Number(e.target.value));
          }}
        >
          {[10, 20, 30, 40, 50].map((pageSize) => (
            <option key={pageSize} value={pageSize}>
              Show {pageSize}
            </option>
          ))}
        </select>
      </div>
    </>
  );
};

export default EventTable;
```

Let's go through all the things happening in this table component - the amount of props being destructured right off the bat can be a little overwhelming at first glance.

Although the actual import list from the `react-table` library is very small - just three separate methods, the depth of functionality contained within expands quickly after the component's declared.

- **[`useTable`](https://react-table.tanstack.com/docs/api/useTable)** and **[`usePaginations`](https://react-table.tanstack.com/docs/api/usePagination)** combined make up all of the properties being destructured at the beginning of the component, which makes sense - this is a table libary we're working with, after all.

From these two hooks we get:

- **`getTableProps`** - the table props from react-table.
- **`getTableBodyProps`** - the table body props from react-table.
- **`headerGroups`** - headerGroups, if your table has groupings.
- **`prepareRow`** - rows for the table based on the data passed.
- **`page`** - necessary for having a paginated table.
- **`canPreviousPage`** - boolean if there are previous pages the table can paginate to.
- **`canNextPage`** - boolean if there are future pages the table can paginate to.
- **`pageOptions`** - an array corresponding to available pages in the table (useful for select interfaces allowing users to type in a page number instead of using buttons).
- **`pageCount`** - amount of pages available based on current page size value.
- **`gotoPage`** - function to set page index to value specified by user.
- **`nextPage`** - function to increase page index by one.
- **`previousPage`** - function to decrease page index by one.
- **`setPageSize`** - function to set page size to a new value.
- **`state: { pageIndex, pageSize }`** - currently set page index and page size for table.

Whew! But after all that initial destructuring the `<EventTable>` component only takes in two initial array props: `columns` and `data`. Note that both of these arrays of values must be **memoized** , according to the React Table [docs](https://react-table.tanstack.com/docs/api/useTable).

- **`data`** is what the `useTable` hook turns into rows and pages of event data.
- **`columns`** are the core columns configuration object for the entire table (put a pin in this array - we'll get to configuring it later in this article).

And after that it's all JSX in this component.

The `headerGroups` are mapped over to render any headers at the very top of the table - our table will only have one header, but you could have multiple headers helping to visually show grouping of columns.

Then, each `page` is mapped over, each `row` of data in that page is extracted and each `cell` in that row is rendered.

Followed by buttons galore and a custom input, which are added to make pagination possible in many different ways; `canPreviousPage`, `nextPage`, and `goToPage`, for example.

And finally, `pageSize`, or the number of rows displayed in each page, and is also made dynamic.

It's a lot of JSX, but it is fairly straightforward once some of the initial mapping and iterating is out of the way.

The nice part though, is that the `react-table` library has all of this functionality _built-in_, and all we have to pass it in terms of data to get all this is two arrays. That's it.

**Style the table**

Now for some very simple styling to make this table presentable looking. As I mentioned before, React Table considers itself more of a headless table utility library, and as such, applies no default styling to any of the tables it generates out of the box.

Luckily, this also means there's no default styling to _undo_, so just a few lines of CSS can make this table look nice.

Wherever you're keeping styles in the Next.js app (I kept all of mine in the `styles/` folder), create a new CSS or SCSS file named `EventTable.module.css`.

> I'm using Sass in my Next project in addition to the built-in **[CSS module styling](https://nextjs.org/docs/basic-features/built-in-css-support#adding-component-level-css)**.
> 
> There's very little Sass nesting going on in this particular SCSS file so I think it will be easy enough to follow, but if you'd like to set up Sass in your own Next project, you can follow Next's directions **[here](https://nextjs.org/docs/basic-features/built-in-css-support#sass-support)**.

**[`EventTable.module.scss`](https://github.com/paigen11/react-gps-asset-tracker-dashboard/blob/main/styles/EventTable.module.scss)**

```scss
.tableWrapper {
  border-spacing: 0;
  border: 1px solid #ededed;
  width: 100%;

  tr:nth-child(2n) {
    background-color: #fafafa;
  }

  th {
    padding: 15px;
  }

  td {
    padding: 5px 10px;
  }
}
```

In this SCSS file we're making a `tableWrapper` class to give the table a light border (`border: 1px solid #ededed;`), alternating the background color of the rows between the default background color or plain white with another off-white color (`background-color: #fafafa;`) for better readability, and adding some padding to the table header (`padding: 15px;`) and table data (`padding: 5px 10px;`).

Not that much styling, but it makes for a polished-enough looking table that goes with the minimalist feel of the rest of the dashboard.

> **Note:** A lot of this code for the `<EventTable>` was inspired by the very good examples in the **[React Table documentation](https://react-table.tanstack.com/docs/examples/pagination)**, and I would strongly encourage you to check it out - it's very thorough and covers many of the table use cases people have.

### Render the table in the app

With our table component done, it's time to add it to the main dashboard component. Import it into the `index.tsx` file in your main `pages/` folder.

I've condensed down the code in this file for clarity, but for the full code in GitHub, you can click on the file title here.

The actual data and columns for the table will be handled shortly - this is just to get the `<EventTable>` into the dashboard page.

**[`pages/index.tsx`](https://github.com/paigen11/react-gps-asset-tracker-dashboard/blob/main/pages/index.tsx)**

```typescript
// imports
import EventTable from "../src/components/EventTable";
// other imports 

type dataProps = {
// condensed for code brevity
};

export default function Home({ data }: { data: dataProps[] }) {
// logic to transform data into the columns and data arrays needed to pass to the table

  return (
    <div>
      {/* extra tracker app code */}
      <main>
        <h1>React Blues Wireless Asset Tracker</h1>
        {/* other tracker components */}
        <div>
          <EventTable columns={columns} data={eventTableData} />
        </div>
      </main>
    </div>
  );
}

// more code down here: getStaticProps
```

This is one of the easiest parts of this whole tutorial: import the `<EventTable>` component at the top of the file, and then pop it into the JSX down below.

Now on to populating this table with events data.

---

## Fetch data for the table

In my **[first asset tracking dashboard post](https://www.paigeniedringhaus.com/blog/create-an-asset-tracker-with-next-js-and-react-leaflet)** I went into great detail about how to create your own asset tracker to generate real data for the app using Blues Wireless hardware and a data fetching function.

If you'd like to follow along there to build your own tracker and route data to Notehub, please be my guest.

For this post, I'll jump ahead to the part where we're already pulling data into the app via a Next.js `getStaticProps` API call. The JSON data from the Notehub cloud looks like this:

```json
[
  {
    "uid": "d7cf7475-45ff-4d8c-b02a-64de9f15f538",
    "device_uid": "dev:864475ABCDEF",
    "file": "_track.qo",
    "captured": "2021-11-05T16:10:52Z",
    "received": "2021-11-05T16:11:29Z",
    "body": {
      "hdop": 3,
      "seconds": 90,
      "motion": 76,
      "temperature": 20.1875,
      "time": 1636123230,
      "voltage": 4.2578125
    },
    "gps_location": {
      "when": "2021-11-05T16:10:53Z",
      "name": "Sandy Springs, GA",
      "country": "US",
      "timezone": "America/New_York",
      "latitude": 33.913747500000014,
      "longitude": -84.35008984375
    }
  },
  {
    "uid": "3b1ef772-44da-455a-a846-446a85a70050",
    "device_uid": "dev:864475ABCDEF",
    "file": "_track.qo",
    "captured": "2021-11-05T22:22:18Z",
    "received": "2021-11-05T22:23:12Z",
    "body": {
      "hdop": 2,
      "motion": 203,
      "seconds": 174,
      "temperature": 22,
      "time": 1636150938,
      "voltage": 4.2265625
    },
    "gps_location": {
      "when": "2021-11-05T22:22:19Z",
      "name": "Doraville, GA",
      "country": "US",
      "timezone": "America/New_York",
      "latitude": 33.901052500000006,
      "longitude": -84.27090234375
    }
  },
  {
    "uid": "e94b0c68-b1d0-49cb-8361-d622d2d0081e",
    "device_uid": "dev:864475ABCDEF",
    "file": "_track.qo",
    "captured": "2021-11-05T22:40:04Z",
    "received": "2021-11-05T22:46:30Z",
    "body": {
      "hdop": 1,
      "motion": 50,
      "seconds": 41,
      "temperature": 21.875,
      "time": 1636152004,
      "voltage": 4.1875
    },
    "gps_location": {
      "when": "2021-11-05T22:40:05Z",
      "name": "Peachtree Corners, GA",
      "country": "US",
      "timezone": "America/New_York",
      "latitude": 33.9828325,
      "longitude": -84.21591015624999
    }
  },
  {
    "uid": "1344517c-adcb-4133-af6a-b1132ffc86ea",
    "device_uid": "dev:864475ABCDEF",
    "file": "_track.qo",
    "captured": "2021-11-06T03:04:07Z",
    "received": "2021-11-06T03:10:51Z",
    "body": {
      "hdop": 1,
      "motion": 126,
      "seconds": 218,
      "temperature": 12.5625,
      "time": 1636167847,
      "voltage": 4.1875
    },
    "gps_location": {
      "when": "2021-11-06T03:04:08Z",
      "name": "Norcross, GA",
      "country": "US",
      "timezone": "America/New_York",
      "latitude": 33.937182500000006,
      "longitude": -84.25278515625
    }
  }
]
```

Each JSON object in this array is a separate `_track.qo` motion event that displays the Notecard's current location and sensor readings. The part of the object that we care about in this particular post is the `gps_location.latitude`, `gps_location.longitude`, and `body.voltage` values. This is the data our table needs.

> If you'd like to save setting up your own asset tracker, you can paste this small set of events into your app's `index.js` file to simulate the data being fetched from Notehub, for testing purposes.

### Transform the JSON data to fit custom table columns and cells

With data coming into our application, we'll manipulate it and create some new columns to display in the table. And this is where things start to get interesting.

**Customize the cell data**

If you notice from the shape of the JSON data coming in, the `gps_location.latitude` and `gps_location.longitude` values are two separate properties in the `_track.qo` event, but it makes more sense to display them together in a single data cell as a comma separated list.

Likewise, the `time` is in epoch time - a very machine readable format, that humans have a tough time deciphering. So for my table cells, it'd be nice to format it into a date that makes sense to people.

Here's how we'll do it - as always, the full code is available for you to review in GitHub.

**[`pages/index.tsx`](https://github.com/paigen11/react-gps-asset-tracker-dashboard/blob/main/pages/index.tsx)**

```typescript
// imports
import { useMemo, useEffect, useState } from "react";
import dayjs from "dayjs";
// other imports

type dataProps = {
// condensed for code brevity
};

export default function Home({ data }: { data: dataProps[] }) {

// state variables for the various pieces of data passed to the table
  const [eventTableData, setEventTableData] = useState<dataProps[]>([]);

  useEffect(() => {
    if (data && data.length > 0) {
      const eventData = [...data].reverse();
      setEventTableData(eventData);
    }
  }, [data]);

  interface row {
    [row: { string }]: any;
  }

  const columns = useMemo(
    () => [
      {
        Header: "Latest Events",
        columns: [
          {
            Header: "Date",
            accessor: "captured",
            Cell: (props: { value: string }) => {
              const tidyDate = dayjs(props.value).format("MMM D, YY h:mm A");
              return <span>{tidyDate}</span>;
            },
          },
          {
            Header: "Voltage",
            accessor: "body.voltage",
            Cell: (props: { value: string }) => {
              const tidyVoltage = Number(props.value).toFixed(2);
              return <span>{tidyVoltage}V</span>;
            },
          },
          {
            Header: "Heartbeat",
            accessor: "body.status",
          },
          {
            Header: "GPS Location",
            accessor: "gps_location",
            Cell: (row) => {
              return (
                <span>
                  {row.row.original.gps_location.latitude.toFixed(6)}
                  &#176;, 
                  {row.row.original.gps_location.longitude.toFixed(6)}&#176;
                </span>
              );
            },
          },
        ],
      },
    ],
    []
  );

  return (
    <div>
      {/* extra tracker app code */}
      <main>
        <h1>React Blues Wireless Asset Tracker</h1>
        {/* other tracker components */}
        <div>
          <EventTable columns={columns} data={eventTableData} />
        </div>
      </main>
    </div>
  );
}

// getStaticProps call to Notehub
```

To make this happen, we'll add in a few React Hooks: `useState`, `useEffect` and `useMemo`, and to make formatting the `time` easier, I added the library **[`dayjs`](https://day.js.org/)** to my project. Not entirely necessary, but it's convenient.

**Get the table data**

Inside of the component, create a new state variable with the `useState` Hook to hold the event data: `const [eventTableData, setEventTableData] = useState<dataProps[]>([]);`.

Next, set up a `useEffect` function that will run when the JSON data is fetched into the component on page load via the `getStaticProps` call. When the `data` is present, we take the data, reverse the order so the most recent events are first instead of last - I think that makes more sense when seeing a list of events here: most recent first - and set those events in the component's state via `setEventTableData`.

**Configure the columns**

The final step is defining the columns for the table.

The first thing we must do is wrap the newly defined `columns` variable in a `useMemo` function (as defined by the React Table documentation), and I'll also give the whole table a header named `Latest Events`.

Then we can set up the `columns` array: I want one for the `Date` stamp, one to display the device's `Voltage`, one for the device's `Status` (i.e. whether the event is a motion event caused by actual motion or it's a "heartbeat" event - an event just to let you know the device is still alive), and one for the `GPS Location`, which as I said before I want to be a combination of latitude and longitude.

Each of these titles will become a `Header` property in each of these objects, and they will be followed by an `accessor` property. Typically, the `accessor` value will be something simple like `body.status`, which is the way to get the actual value out of the object. If the `accessor`, however, needs to be transformed before being set, this is where the `Cell` function comes into play.

**[Cell](https://react-table.tanstack.com/docs/api/useTable#cell-properties)** is a method that can be be used to transform the props in that cell and completely customize the return value, right down to the JSX.

So for example, to modify the timestamp date into a nicely formatted string, we grab the `captured` value out of the event, and then use `Cell` to run a `dayjs` function and format the date into a new variable named `tidyDate`. And then we return `tidyDate` as a bit of JSX wrapped in a `<span>` tag.

Similarly, to concatenate the latitude and longitude values together into one cell, we have to go even deeper into the props being passed to `Cell`, and pull out two of the properties nested inside of the `gps_location` object to return in the cell.

> **`Console.log()` is your friend here.**
> 
> Don't be afraid to reach for `console.log()` when putting together these complex data cells. Only by using it to see the shape of the data and each row in the table was I able to figure out how to extract the values I needed for the `GPS Location` column.

And there you have it: our `columns` are defined and our `data` is too, pass both to the `<EventTable>` component, and we should be good to go.

---

## Conclusion

About 9 months ago, I began working for an IoT startup, and learning how to manage and display readings in dashboards from devices "on the edge". So one of the first projects I built to this end was a dashboard including data charts, a map showing device location, and a table listing all the events being displayed here.

To render a table with all the flexibility and none of the cruft, I turned to the React Table library, a headless and highly extensible set of hooks that simplifies building complex tables, handling things like pagination, page size, and so much more, with very little effort on our part. Even modifying the display of table cell data in a row, is relatively straightforward. It's a great library.

This dashboard actually came in handy when my parents' car was stolen from their driveway the night after Thanksgiving. If you want to hear the whole story and build your own tracker, check out this **[blog post and video](https://blues.io/blog/gps-asset-tracker-with-blues-wireless-and-react/?&utm_source=dev.to&utm_medium=web&utm_campaign=niedringhaus-effect&utm_content=ep-2)** I made for Blues Wireless - it details the whole process from hardware to software to deploying to Netlify.

Check back in a few weeks — I’ll be writing more about JavaScript, React, IoT, or something else related to web development.

If you’d like to make sure you never miss an article I write, sign up for my newsletter here: https://paigeniedringhaus.substack.com

Thanks for reading. I hope you enjoyed learning how to leverage the React Table library to make a data table to display event data from an IoT asset tracker. Tables can be tricky to get right, but they're such a nice, straightforward way to display all sorts of data to users.

---

## References & Further Resources

- [Hackster.io original asset tracker project](https://www.hackster.io/paige-niedringhaus/low-code-gps-asset-tracker-and-map-display-b10419)
- [Asset tracker GitHub repo](https://github.com/paigen11/react-gps-asset-tracker-dashboard)
- [React Table](https://react-table.tanstack.com/) library 
- Blues Wireless [website](https://blues.io/?&utm_source=dev.to&utm_medium=web&utm_campaign=niedringhaus-effect&utm_content=ep-2)
- Blues Wireless [developer experience site](https://dev.blues.io/?&utm_source=dev.to&utm_medium=web&utm_campaign=niedringhaus-effect&utm_content=ep-2)
- Original article about [rendering maps in a Next.js application](https://www.paigeniedringhaus.com/blog/create-an-asset-tracker-with-next-js-and-react-leaflet)
- Follow up article about [custom styling charts and tooltips with React Recharts library](https://www.paigeniedringhaus.com/blog/build-and-custom-style-recharts-data-charts)