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):
I. 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
ornpm 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 themyarn 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
The 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 fromreact
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
- 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
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.
Next steps:
- Display global data - all cases, all deaths, all recovered and maybe all vaccine taken if I find the data.
- Add searching to the table but I would like to join it somehow with the map.
- Create custom markers, redesign popups and add layers to the map.
Top comments (0)