A COVID-19 Dashboard in React

Sam Elsamman
Level Up Coding
Published in
10 min readApr 29, 2020

--

Completely Configurable Dashboard with Animations

Every year software development gets easier and easier. We build on the shoulders of giants. Not just Facebook, Google, and Amazon but the many smaller companies and individuals that create “cool” and useful pieces of the jigsaw puzzle. In short building software is fun again because you can leverage what came before you and focus only on the problem at hand.

With extra time on offer during the COVID-19 pandemic I set out to write a dashboard that would let me customize everything, from the cities and countries to the types of data presented and even the layout. I was obsessed with pandemic data and wanted a tool to be able to easily create charts and graphs on the fly to share or illustrate points. I had not seen a dashboard that allowed such flexibility so that was my excuse for creating my own:

https://www.cdash19.com

It is open source, hosted on Amazon S3, and distributed using their Cloudfront CDN. Every day a Lambda job runs to pull new data from the Johns Hopkins University repo on github. The application is a mere 500MB (transfer size) including data for all countries in the world and county-level data in the US. An initial version was up and running in a couple of weeks.

I am a retired tech entrepreneur and co-founder of Haven Life. I started out with punched cards and assembly language. For me the most interesting thing was the process of creating and distributing the app. I was surprised to find it a lot smoother than expected. It kept me sane during difficult times.

React — My New Favorite Web Platform

React is one of the most productive platforms I have used to build a single-page web app. Oddly I was introduced to it by writing a react-native app where the main attraction was having a single source for both Apple and Android. In the process I began to fall in love with the clean and simple structure of React itself but had never written a web app with it.

React for the web is far more mature than react-native. It has a very deep ecosystem of ready made components and libraries for just about anything you need. I chose react-bootstrap for layout and the very rich Victory Charts for the graphs along with a plethora of other libraries for specific needs.

While the component structure of React is fabulous, managing state can be a royal pain and it is easy to spend way too much time doing so. There are just too many ways of doing it and all involve a fair amount of non-productive boiler-plate code. This was the first web app I wrote with the redux-capi state management library which I introduced earlier this year. It made managing state a non-issue. More on that later.

Create-react-app

React is a complicated stack when you consider everything involved in packaging up a transpiled app and web-packing it with all of the libraries you casually imported on a whim. create-react-app takes the complexity out of this and delivers a compact bundle ready for deployment.

The price you pay is that it is ready-made and so configuration is rather limited. I came up with no cases that required custom configuration. If, however, you outgrow it, “eject” lands you with a normal React app.

It requires node Node JS (8.10 or higher) and npm (5 or higher). After that you can use yarn or npm for package management. You create your website and launch it for testing into your browser with these commands:

npx create-react-app website
cd website
yarn start

Serverless

There are many choices for deploying an app. In fact almost too many and the decision can be daunting. In the past I had always stood up a server in the cloud to get started and later regretted it as my needs grew. Sometimes it helps to know where you will end up before you start.

Since this site is static (has no server components) the easiest and the most robust and scalable way to do this was to go serverless. Amazon was the logical choice since I had an account and some familiarity with it. Microsoft Azure and Heroku are also options.

Amazon Web Services (AWS) has a steep learning curve and is not for the faint of heart. Enter the Serverless deployment platform that takes all the pain out of deploying apps and services. In a couple of minutes you can deploy a static site to S3 (cloud storage) and have it distributed through their CloudFront (CDN). In the event you need an API or in my case a data fetching script run periodically, Lambda (Javascript on demand) fits the bill easily and is equally easy to deploy with Serverless.

You install it globally with:

npm install -g serverless

Now add a serverless.yml file to the react project you just created.

component: website
name: myapp
stage: dev

inputs:
src:
src: ./src
hook: npm run build
dist: ./build

In order to deploy the app to AWS you need to sign up for AWS and grab your credentials. In involves a few steps which you can skip until you are ready:

In AWS under services find the IAM dashboard. Click on Users , and then Add User to create a new user. Give it a name (like websitedeploy ) and check Programmatic Access, When you click Next, select Attach existing policies directly and check AdministratorAccess. Hit Next twice and when on the tags screen hit Create. “Next” again to skip the tags screen, then hit “Create”. Assuming your computer is secure and you are comfortable having important credentials on it copy and paste the Access Key ID and the Secret Access Key into an .env file in the deploy directory that you just created:

AWS_ACCESS_KEY_ID=access key id you just copied
AWS_SECRET_ACCESS_KEY=secret access key you just copied

Be sure to add .env to your .gitignore file!

With your secret decoder ring complete and you can now deploy to AWS with one command:

serverless deploy

After it spins for a bit you will see:

serverless ⚡ framework
Action: "deploy" - Stage: "dev" - App: "myapp" - Instance: "myapp"
bucket: website-xxxxx
bucketUrl: http://website-xxxxx.s3-website.us-east-1.amazonaws.com
url: https://xxxxx.cloudfront.net
75s › myapp › Success

That’s it. Wait a minute or so for it to propagate and go to the URL to see your website deployed. Want to give it a domain name and use the Amazon free SSL certificate? Just register it on AWS Route 53, add it to the YML file and do another deploy.

inputs:
src:
src: ./src
hook: npm run build
dist: ./build
domain: mydomain.com

You now have a custom domain on a world class infrastructure. Best of all the cost is de minimis. Probably nothing for a long while with the free tier. I am past that, however, and estimate that hosting will cost about $5/million visits.

State Management in React

My assumption from here on in is that you have at least dabbled in React. If not and you have basic HTML and Javascript ES 6 skills React is not terribly hard to learn.

Since React introduced functional components, much of the boilerplate code that used to inhabit your screen space is gone. Now you have nice and tidy little class-free components like this one that lets you check off which data properties you want included in a chart or table:

export default ({id, dataPoints, max, scale}) => {    const {widget, addPropToWidget, deletePropFromWidget} =
widgetsAPI({id: id});
const onChange = prop => widget.props.includes(prop)
? deletePropFromWidget(prop) : addPropToWidget(prop);
const isDisabled = prop =>
widget.props.length >= max && !widget.props.includes(prop);
return ( // What is rendered for each widget
<Form>
<Form.Row>
{Object.getOwnPropertyNames(dataPoints).map(prop=> (
<Form.Group as={Col} key={prop}>
<Form.Check
style=
{{fontSize: 11 * scale}}
onChange={()=>onChange(prop)}
label={dataPoints[prop]}
disabled={isDisabled(prop)}
checked={widget.props.includes(prop)}
/>
</Form.Group>
))}
</Form.Row>
</Form>
)
};

This component is passed the id of the widget (graph or chart) that it is to render in its properties along with some other configuration details. It gets everything it needs from the redux store using widgetAPI which is an API you build with redux-capi. The API interfaces between the component and redux.

There are alternatives for state management such as MobX and GraphQL but redux is still the standard for dealing with the immutability requirements of React. redux-capi just makes redux a lot easier to deal with. It automates the way you create the usual redux elements — actions, selectors and thunks.

Here it provides widget (a selector in) as well as addPropToWidget and deletePropFromWidget (self-dispatching actions). The component logic is then just about the presentation. In this case just mapping the data properties that can be included in the widget into checkboxes on the screen. As you check or uncheck the box they are added or removed from the widget props array in redux with the two self-dispatching actions.

So where are those ugly reducers your have to write for each action? There aren’t any. With redux-capi you create a declarative description of how actions affect your state shape and redux-capi does the reducing for you.

The state shape looks like this:

widgets: [
{id: 1, props["deaths", "cases"]}
{id: 2, props["cases"]}
]

The declarations, known in redux-capi as “redactions” for addPropToWidget and deletePropFromWidget describe how to navigate to the correct widget array element and in the case of deletePropFromWidget how to navigate to the right props array element.

addPropToWidget: (prop) => ({
widgets: {
where: (state, item, ix, {id}) => item.id === id,
select: {
props: {
append: () => prop
}
}
}
}),
deletePropFromWidget: (prop) => ({
widgets: {
where: (state, item, ix, {id}) => item.id === id,
select: {
props: {
where: (state, item) => item === prop,
delete: true
}
}
}
}),

The highest level function is a template for your action and you declare the arguments the action will expect. It returns a “schema” of how the state is to be mutated that will be passed to the master reducer in redux-capi.

In this case the widgets property in the state will be affected and more specifically the instance where the id matches the id in the context. Context? What context? Each time you use the widgetsAPI in a component you establish a context for that instance of the component. There are many widgets on the screen and each could be connected to a different widget instance. Each component instance, however, is connected to only one widget and it passes that id into the context when calling the widgetsAPI

const {widget, addPropToWidget, deletePropFromWidget} =
widgetsAPI({id: id});

The context is also used in the widget selector which is defined like this:

widget: [
(select, {widgets, id}) => select(widgets, id),
(widgets, id) => widgets.find(w => (w.id === id))
],

Selectors have a built-in memoizing capability so that they are not recalculated unless the underlying values change. Here the selector is dependent on the collection of widgets (as defined by another selector call widgets) and the id (in the context). If either changes the second function is called to recalculate the value and is passed in these properties as parameters.

There are also thunks which are not hugely different than redux thunks except that they have access to the context for instance-specific data like the id as well as all other thunks and selectors in that API. As the API grows it can easily be composed and split into smaller parts since it has few external dependencies and gets most of what it needs from the context.

Animated World Map

Ever been to a tech meetup where the presenter of a dry subject, like say closure optimizations, uses a cool CSS animation and all the questions are about the animations rather than closures? I would be remiss if I didn’t talk about the world map animation.

The folks over at Simple Maps have a an SVG map of the world that they generously license for free. React, however, doesn’t play well with complex SVG because as we know JSX is not exactly HTML. The good news is that in the latest release they implemented a way to import an SVG as a component.

import {ReactComponent as World} from "./world.svg";

Unfortunately create-react-app is configured to not accept namespace tags which world.svg was using. After running it through this nifty tool at https://jakearchibald.github.io/svgomg/ all the namespace tags were removed and the SVG was generally optimized.

Now we need to set the color of each country independently through CSS. The SVG used a unique data-id for each county so it was a simple matter of editing the SVG source and doing a search/replace to change data-id= to id=. Now CSS rules can be applied.

CSS is, of course, static and we want to change the color based on the country-specific data and to animate that over time. The styled-components package was perfect for this. It allows you take a component and apply styles to it from a string.

import styled, {keyframes, css} from 'styled-components'

Then you need to calculate the style string that will have the animations:

function getStyledWorldAnimated (cols, countries, dataPoint, mode) {    const styles = countries
.filter(c => c && c.type === 'country')
.map( data => {
const range = data[dataPoint];
const last = data[dataPoint][range.length - 1] || 0;
const fade = range.map((m, i) => m > 0 &&
(i === 0 || range[i - 1] !== m)
? `${i * (i)/range.length}% \{fill: ${colors[m]}\};`
: "")
const animation = keyframes`${fade}`;
return (css`#${data.code} \{fill: ${colors[last]};
animation:
${animation} 1 ${animationTime}s forwards\};`)
});

return (styled(World)`${getSizeStyle(cols, mode)}${styles} `);
};

The heart of the animation is in the code that maps a style rule to a percent-based keyframe for each date:

const fade = range.map((m, i) => m > 0 && 
(i === 0 || range[i - 1] !== m)
? `${i * (i)/range.length}% \{fill: ${colors[m]}\};`
: "")
const animation = keyframes`${fade}`;

The code runs through the range of values, optimizing for ones that don’t change values and produces a key frame like “6.2% {fill: #447766}” . The keyframes are passed to the keyframes helper and you have an animation. This animation is then run through the css helper to produce an animation rule for each country.

return (css`#${data.code} \{fill: ${colors[last]};
animation:
${animation} 1 ${animationTime}s forwards\};`)

Finally with all the css animations for each country collected in an array you pass them to styled along with the World component you imported from the SVG. You end up with a component that will be bound to the animation rules. Under the covers it will dynamically add all of the CSS styles at run-time and attach just one class to your component.

return (styled(World)`
width: 100%;
height: auto;
${styles} `);

Note that using width 100% SVGs won’t scale properly with IE11 (though it does work with Edge). Victory Charts don’t either. Life is too short.

Conclusion

Standing up a simple single-page app is a lot easier than it used to be. Large scale apps have many other considerations for selection of platforms and in really high volume situations serverless solutions may actually cost more but for small non-mission critical sites you can now get a huge bang for your buck with tools such as create-react-app and Serverless. Happy computing!

--

--

Retired technology entrepreneur who loves to hike, travel, cook and write music.