Coding Journal

My journey to become a Full Stack Developer

How to create tables with  React Table library - Covid Map project day 4.

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. 

Next steps:
1. Display global data - all cases, all deaths, all recovered and maybe all vaccine taken if I find the data.
2. Add searching to the table but I would like to join it somehow with the map. 
3. Create custom markers, redesign popups and add layers to the map.

 

Tagged in : built-in-public

my photo

written by

Makneta

Avid learner, Python / Django and CSS Art ethusiast.

Similar posts

All posts