How to create tables with React Table library - Covid Map project day 4.
May 2, 2021, 10:35 a.m.
In this series of posts, I'm describing my learning process while creating a React project. I'm quite awful at learning from courses and my preferred method is coming up with an idea for a project and then trying to find the solution to problems by reading docs, blog articles, watching bits of tutorials.
The main part of the project is a map with a marker for each country. When you click the marker, the popup will appear with information about Covid cases in that country. I have already described how to add leaflet.js map and how to create markers for each country .
But I thought that it could be also useful to see the same info as a table.
I have data fetched (I was writing about it yesterday: How to fetch data from more than one API)
Things I've done (problems and my solutions):
1. At first I was thinking about doing the list of countries as a scrollable sidebar. But I didn't like how it looked like. Then I wanted to create a table but I didn't know how to make a table responsive or rather again scrollable and I also started wondering what would be the best way to add data to the table and then make it searchable and sortable.
I could spend time trying to reinvent the wheel but I decided to look for a library that can help me. Part of me still thinks that it's cheating but I keep convincing that part that using different libraries is also a skill.
I didn't want any massive CSS UI library so I decided to use react-table one.
How to add react-table to the project?
* It is easy to start by adding ` yarn add react-table` or `npm install react-table --save`
And then we can copy and paste quite a lot of code from documentation. They've got many examples on codesandbox.io.
I'm trying to create reusable components as well as separate UI components from the rest so I divided the code into 3 part.
* First I created TableElements.js component in folder components/modules and there I pasted the CSS part. I'm using `styled-components`. So first I had to import them `yarn add styled-components`. And now time for my TableElements.js
import styled from 'styled-components'
export const Styles = styled.div`
table {
border-spacing: 0;
border: 1px solid #e8eaed;
overflow: auto;
font-size: 0.9rem;
tr {
:first-child {
display: none;
}
:last-child {
td {
border-bottom: 0;
}
}
}
th {
:nth-child(2) {
text-align: left;
padding-left: 0;
}
}
th,
td {
margin: 0;
padding: 1rem;
border-bottom: 1px solid #e8eaed;
border-right: 0;
:last-child {
border-right: 0;
}
}
}
`;
At first, I only changed the borders' colour and added `overflow:auto` to make the table scrollable.
* Now I created Table.js component where I put all the code from the docs. It builds the table UI.
import React from 'react'
import {useTable} from 'react-table'
const Table = ({ columns, data}) => {
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
} = useTable({
columns,
data
})
return (
<table {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<th {...column.getHeaderProps()}>{column.render('Header')}</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.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
Next step was adding the table to the CountryList.js component.
* First we need to import Styles from TableElements.js, the Table.js component as well as useMemo hook from `react`
import React, {useMemo} from 'react'
import {Styles} from './modules/TableElements'
import Table from './Table'
* Then I have to pass countries data. I was showing last time how it goes through App.js to TableSection.js and then to CountryList. Yes, I'm jumping a bit between components.
My plan was to reuse the TableSection to show there different kinds of tables. In the meanwhile I also created some elements of this section using styled-components (but won't be showing them all here)
// TableSection.js
import React from 'react'
import CountryList from './CountryList'
import {StyledStatSection, StyledTableSection} from './modules/Sections'
import { SectionTitle} from './modules/Titles'
import {StyledButton} from './modules/Buttons'
const TableSection = (props) => {
return (
<StyledStatSection>
<SectionTitle>Statistics</SectionTitle>
<div>
<StyledButton primary>Cases</StyledButton>
<StyledButton>Vaccines</StyledButton>
</div>
<StyledTableSection>
<CountryList countries={props.countries} />
</StyledTableSection>
</StyledStatSection>
)
}
export default TableSection
* The whole useMemo() hook is taken from the documentation's example. I had to change only the headers into my own titles of columns. Accessors are used to build a data model for the columns. So in each column, I was taking the name of the variable from the API as an accessor.
const CountryList = ({countries}) => {
const columns = useMemo(
() => [
{
Header: "Cases",
columns: [
{
Header: "",
accessor: "countryInfo.flag",
},
{
Header: "Localization",
accessor: "country"
},
{
Header: "All Cases",
accessor: "cases",
},
{
Header: "Today's Cases",
accessor: "todayCases",
},
{
Header: "All Deaths",
accessor: "deaths",
},
{
Header: "Deaths Per Million",
accessor: "deathsPerOneMillion",
},
{
Header: "Deaths Today",
accessor: "todayDeaths",
},
]
}
], []
)
return (
!countries ? (<p>Loading...</p>) : (
<Styles>
<Table columns={columns} data={countries} />
</Styles>
)
)
}
export default CountryList
4. So the table was working but I wasn't pleased with two things. I wanted to have flag images in the first column and I also wanted to format large numbers. All of this is possible because we can pass not only strings to accessors but also functions.
* In the first column I added a fat arrow function that gets the cell's value - link to an image and passes it into `<img>` tag
Cell: ({cell: { value } }) => <img src={value} alt="Flag" width={30} />
* The 3rd column and the next ones are displaying numbers. It's hard to read large numbers without any spaces so I created a small function to change it.
const formatLargeNums = (value) => {
return value.toLocaleString().replace(/,/gi, " ")
}
And then I'm again adding it to the useMemo() hook
Cell: ({cell: { value }}) => formatLargeNums(value)
So my useMemo() hook again:
const columns = useMemo(
() => [
{
Header: "Cases",
columns: [
{
Header: "",
accessor: "countryInfo.flag",
Cell: ({cell: { value } }) => <img src={value} alt="Flag" width={30} />
},
{
Header: "Localization",
accessor: "country"
},
{
Header: "All Cases",
accessor: "cases",
Cell: ({cell: { value }}) => formatLargeNums(value)
},
{
Header: "Today's Cases",
accessor: "todayCases",
Cell: ({cell: { value }}) => formatLargeNums(value)
},
{
Header: "All Deaths",
accessor: "deaths",
Cell: ({cell: { value }}) => formatLargeNums(value)
},
{
Header: "Deaths Per Million",
accessor: "deathsPerOneMillion",
Cell: ({cell: { value }}) => formatLargeNums(value)
},
{
Header: "Deaths Today",
accessor: "todayDeaths",
Cell: ({cell: { value }}) => formatLargeNums(value)
},
]
}
], []
)
At the moment (after adding some more styling) the table looks like this:
* As I wrote in the beginning I also wanted the table to be sortable. It's pretty easy with react-table. In the Table.js I had to add {useSortBy} in imports as well as in const at the top of the Table function (in Table.js component)
import React from 'react'
import {useTable, useSortBy} from 'react-table'
const Table = ({ columns, data}) => {
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
} = useTable({
columns,
data
},
useSortBy
)
return (
// ....the same code as before
and then inside the return part of the function, we need to add `getSortByToggleProps()` to `<th>` tag together with className for descending and ascending sorting.
<th {...column.getHeaderProps(column.getSortByToggleProps)}
className={
column.isSorted
? column.isSortedDesc
? "sort-desc"
: "sort-asc"
: ""
}
>
{column.render('Header')}
</th>
Now, when we click on the column's header it sorts out the data but to make sure if it's descending or ascending order we can add arrows in CSS inside our `table` in TableElements.js / Styles
.sort-desc {
:nth-child(n+3) {
box-shadow: none !important;
&:after {
content: "↓";
float: right;
padding-left: 2px;
}
}
}
.sort-asc {
:nth-child(n+3) {
box-shadow: none !important;
&:after {
content: "↑";
float: right;
padding-left: 2px;
}
}
}
And I'm done with the Table part of my project. For now.
As you could notice, I wanted to add a table with data about vaccine coverage but I'm not sure about it.