How To

How I turned a React tutorial into a complete application

20 Jul , 2017  

Hey everyone!

Recently I asked everyone on the lukefabish.com newsletter if they’d like to see how I went from a basic React turorial:

React Timer Component

To this:

react timer application

They said YES 🙂

The writeup covers a lot of ground – from CSS to testing React with Enzyme – but it isn’t a technical tutorial.

I describe some of the more interesting bits in detail, but I wanted to give you an overview of going from a bare-bones implementation of basic functionality to a complete application.

But before we get into that, I should give you a little background on why I built the thing in the first place.

The Pomodoro Technique

If you don’t know about the Pomodoro Technique, you should check it out. It’s a great way to get a lot of focused work done sustainably. I like it, and have used it a lot in the past.

Well, it was time for me to use it again, so off I went to look for simple timers.

I didn’t really like what I found, and thought –  hey – I made a timer as part of that React tutorial; why don’t I use that?

And that’s what I did.

The best part?

Once I’d finished building the app, I eventually came up with a cool domain name (after going through a bunch that were already taken) and Godaddy had a sale going, so I got the domain for five bucks. Yeah!

Then hosted it on Amazon’s S3, which will cost, like, $0.04 per month.

So a weekend of effort and $5 plus change has got me the timer I wanted!

This is how I work with it now:

React pomodoro timer in use

Let’s see how the build went.

Designing the React Pomodoro Timer

Here’s a funny thing about me – I have to feel excited about the way a project looks for me to be motivated to build it.

There are exceptions of course – fun API interactions are great too, but mostly I like cool looking things.

Even so, I started with a sketch. Not with the Sketch app, I mean pen and paper. Like this:

Timer application sketch

I had a pretty clear idea of what I wanted from the start, but I changed it around as I went. As you can see, physical media is not my forte!

I used ‘ID’ at the top for ‘Identity’ – I had no idea what I’d call it at this stage.

Then, I went to Photoshop (something like GIMP would have worked just as well), and hashed out a version of the interface.

This is what I ended up with first:

Greyscale image prototype

You can see that I changed some things around, mostly just the graph at the bottom, and moved the UI components around a bit.

I started in grayscale because… I get confused by color! It distracts me from getting the design finished. I actually enjoy the austerity of this look…but I don’t think anyone else does much.

And I implemented it in grayscale! I exported the background and saved it in a new directory.

First implementation of the React Pomodoro Timer

This is how I got started:

I made a copy of the project that implemented the timer from the tutorial.

Then I turned it into a ‘proper’ application project by initializing npm in the project directory, creating app, test and public folders, and then splitting out the components in the single original source file into separate files. Now the directory looks like this:

timer-app/
  app/
    TimerDisplay.jsx
    TimerForm.jsx
    Timer.jsx
  test/
  public/
    css/
    img/
    js/
    index.html
  package.json

Then I configured webpack to build the application, ran it, and made sure it still does the same thing as before.

Progress! 😛

Then I made a ‘container’ component called ‘App’ and put the Timer component inside that, and made sure it still displayed.

Before doing anything else to it, I added some simple tests for each component to make sure they work in a minimal way.

And these were really simple, minimal tests.

I just wanted to have something to build on when I made more changes.

If I don’t do that early with a personal project, it’s too tempting for me to just forge ahead with the fun part (changing stuff and trying it out in a browser) and getting to the tests ‘later’ (like, ‘never’).

Here’s an example:

import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import TimerDisplay from '../app/TimerDisplay.jsx';

describe('TimerDisplay', () => {
  const nullFn = {};
  it('displays 0 as padded-zero time', () => {
    const wrapper = shallow(
      <TimerDisplay
        timerVal={0}
        timing={false}
        toggleTimerFn={nullFn}
        resetTimerFn={nullFn}
      />
    );

    expect(wrapper.find('.timerDisplay').text()).to.equal('00:00');
  });
});

That’s a test to make sure the timer’s display component displays zero minutes as ’00:00′.

Time to make some real changes

Then I added a couple of buttons beneath the timer in the TimerForm component and duplicated the HTML <select> for choosing time.

I knew I wanted a graph to show how much work I’d done for a given day, but had no idea how I’d do it, so I left that alone for now.

Finally I wrapped everything in a containing component, timerApp, to use my exported image as a background image. Now it looked like this:

React Pomodoro timer minimal implementation

Jolly good.

I started off by changing the size of components to match my layout.

I had the idea by this time of calling the app ‘Uptik’ which I later found out is a personal finance management app! Damn. I was seriously disappointed by this because I just loved the way ‘Uptik’ looks in the font I chose, Nunito Sans.

Anyway, by fiddling with the font size, padding and margins, I got the layout looking like this:

Minimal react timer resized elements

Time to get that graph sorted out.

Implementing an HTML+CSS graph to show how much I’ve worked

Or little!

Anyway,  it’s time to tackle the graph. I wanted to have a graph that’d show how much time I’ve worked each day. I find this quite motivational (what can I say, I’m a simple creature).

This was new ground for me – every other time I’ve done something like this I’ve used a third-party library.

That was because I couldn’t predict the range of data the graph would display, what the labels would be, or what changes clients would want to make to it in the future. A comprehensively implemented graph library would just make life easier.

This time though, I really didn’t want the extra weight of yet another JavaScript library for the browser to download – React.js is overkill for an app like this already! Also, I can control what data it displays as well as the labels (there are none!).

So I set about trying various implementations that failed, and failed, and failed some more. My graph columns came out looking variously like matchsticks, rabbit teeth and tombstones.

Eventually, after googling about (hello StackOverflow!), I found someone talking about doing this by styling the graph as a table. That makes sense. It’s easy to make table cells evenly spaced and sized.

So I embarked on implementing my graph as a table, and populated it with dummy data. It eventually looked like this:

React pomodoro timer first graph implementation

You can see I had my domain name by now, but the graph is falling off the bottom of the background image! Never mind 😉

Now it was time to record the amount of time I work each day.

Recording work time in localStorage

If you haven’t run across it before, localStoraged is pretty cool. It’s a key-value store in your browser is saved, even if your close your browser. To read more about it check out this link.

On a mobile device, localStorage can be capped at as little as 2.5Mb, but that’s plenty to store my daily work record.

I don’t know if you read the original timer article, but these are the important parts for keeping the timer running:

updateTimeRemaining(){
  const updatedTimeRemaining = this.state.timeRemaining - 1;

  if (updatedTimeRemaining <= 0) {
    new Audio('./crash-acoustic.wav').play();
    this.stopTimer();
  } else {
    this.setState({
      timeRemaining: updatedTimeRemaining
    });
  }
}

startTimer(timeMinutes) {
  const intRef = setInterval(this.updateTimeRemaining, 1000);
    this.setState({
    timerIntRef: intRef,
    timeRemaining: timeMinutes * 60
  });
}

componentDidMount() {
  this.startTimer(this.state.time);
}

This is from the main Timer React component. Briefly, what happens is:

  1. React mounts the component, which calls startTimer()
  2. startTimer() calls setInterval to make the updateTimeRemaining function every second, and stores the amount of time remaining in seconds (minutes * 60), and the reference to setInterval in the component’s state.
  3. Every time updateTimeRemaining executes, it reduces the time remaining by 1, checks to see if the time remaining is less than zero and then either stops the timer or stores the remaining time.

Note: step 3 is deeply flawed, and is in for a major overhaul later in this writeup!

Anyway, each time updateTimer() is called, I need to update the amount of time I’ve worked so I can show it in the graph!

Introducing the TimerStore object

Because I can see this app using online storage at some point, I decided to put all my data updates and reads into a separate object called TimerStore.

Hopefully, if I decide to graduate storage from the browser into the cloud, I’ll be able to restrict my changes to TimerStore, and the timer application can remain unchanged.

The external API for the timer store looks like this:

  • incToday() – increments the time worked for today by one minute.
  • getDayStats() – returns a list of dates, with time worked for each day (I think that name’s pretty unclear, it’ll have to change!)

Internally, the list of days is stored with the key timer-dates, which I reference with a constant called TIMER_DATES.

(There’s a lot more going on in TimerStore than this to manage dates, updating counts etc, but I won’t go into it here to save on time.)

The date data is stored in a JSON object that looks like this:

[
  { date: <the date>, count: <number of minutes worked>},
]

So the data is saved to local storage like this:

localStorage.setItem(TIMER_DATES, JSON.stringify(dateData));

Where dateData is an array of {date: <date>, count <minutes worked>}, and JSON.stringify() turns it into a nice JSON object.

The data is read like this:

let dateData = JSON.parse(localStorage.getItem(TIMER_DATES));

So back to the timer application. With the timer store in place, I can now update the work for the day by doing this:

updateTimeRemaining(){
  const updatedTimeRemaining = this.state.timeRemaining - 1;

  TimerStore.incToday();

  if (updatedTimeRemaining <= 0) {
    new Audio('./crash-acoustic.wav').play();
    this.stopTimer();
  } else {
    this.setState({
      timeRemaining: updatedTimeRemaining
    });
  }
}

startTimer(timeMinutes) {
  const intRef = setInterval(this.updateTimeRemaining, 1000);
  this.setState({
    timerIntRef: intRef,
    timeRemaining: timeMinutes * 60
  });
}

componentDidMount() {
  this.startTimer(this.state.time);
}

Populate the graph with work data

Now that I’ve finally got some work data going in, I’d better get it out and put it in the graph.

So I replace my hard-coded graph bars with the following:

export default function TimerGraph() {
  const days = TimerStore.getDayStats();
  // Pad dates with empties so graph doesn't look weird.
  if (days.length < 7) {
    for (let i = days.length; i < 7; i += 1) {
      days[i] = TimerStore.newEntry(todayStrLess(new Date(), i));
      days[i].date += 'Padding'; // Avoid conflict with actual data
    }
  }
  days.reverse();

  return (
    <ul className="timerGraph">
    {
      days.map(day => (
        <li key={day.date} >
          <span
            className="graphBar"
            style={{
              display: (day.count > 0) ? 'block' : 'none',
              height: `${dayCountToHeight(day.count)}%`,
            }}
          />
        </li>
      ))
    }
    </ul>
  );
}

That code’s ripe for refactoring, but the important things to know here are:

The first part of the code gets all available data, and then pads it out if there’s less than 7 days to make sure the graph fills correctly.

Then it reverses the data to make sure the most recent date is right-most in the graph.

Then it returns a <ul> with class name timerGraph (it has display: table) populated with <li> elements with class set to graphBar (with display: table-cell) and their height set to the number of minutes worked expressed as a percentage of 12 hours (that’s what dayCountToHeight() does).

12 hours may be a bit too much. I rarely have more than 8 hours of time sitting and working at the computer.

So now the timer looks like this:

React timer tikti.me first real graph bar

Coloring the timer application

I’m finally sick of the application being greyscale. So I went and tried to pick some colors for it in Photoshop.

I eventually had my best (well, my favorite) result just by putting a color overlay on top of the original design.

Here’s what my Photoshop file looks like:

tikti.me react pomodoro timer artboards

Which is your fave? The orange-y one is where I tried to hand-pick the colors.

So I re-exported the background and replaced my greyscale background with the reddish background.

Then I used Photoshop’s eyedropper to see what color the text came out as. I recorded that value and used it to update the timer’s CSS to change the color of the text in the application.

So now it looks like this:

Timer with added color

Working AND Resting in the React timer application

That’s all great, but at the moment the timer only shows work time, and doesn’t count down rest time.

So here’s what I do:

I add some state to the Timer component: timingWork, which I initialize as true.

Then, each time updateTimeRemaining is called, it checks the time remaining to see if it’s zero. If it is and timingWork is true, the code restarts the timer with our rest time, and timingWork is set to false.

Otherwise, the timer just stops.

Implementing the graph bar shadow

So you saw my graph-bar code before, right? Go back and check it out if you need a refresher, because I’m omitting it here for the sake of “brevity” (this is 3K words and counting).

Anyway, to summarise, it represents the graph using a bunch of <li> elements inside a <ul>, natch.

If we take a close look at my mockup, you’ll see the graph has some shadows behind it:

Timer graph shadow detail

I know a lot of people don’t even notice those shadows, but I really like them.

They skew the perspective, but that’s one of the things I enjoy doing with the current ‘flat’ design the Internet has adopted. The timer looks flat, the graph looks non-flat, and together look wrong in a way I like 🙂

Anyway, how was I going to get those shadows?

Once again I tried a lot of different things by styling the ::after pseudo-class for the graph bars, but that didn’t cut it.

In the end, here’s what I did:

  • I displayed two <span> elements inside each <li>: one for the graph bar, another for the shadow.
  • I set the z-index of the graph bar to 1, and the z-index of the shadow to 0
  • I made the shadow the same height as the graph bar + 10% (because shadows are often longer or shorter than their caster).
  • Skewed the shadow to make it fall at an angle: transform: skew(-10deg);
  • I used linear-gradient as a background image for the shadow with the following CSS: background-image: linear-gradient(0deg, rgba(20, 0, 200, 0.1), rgba(20, 0, 200, 0.1));

(Note that the shadow is a light-purple color. “Purple adds depth to shadows,” my oil-paining mother told me when I was a boy).

That’s the first time I’d used linear gradient, so it

  1. Took some learning, and
  2. Was lots of fun!

But now I had another problem, the graph looked like this:

Timer graph bar misplaced shadow

So I had to move that shadow across a certain amount, based on its height and its 10 degree skew angle.

This requires some math-ing, and that is not my forte.

I head back to Google, where I found this StackOverflow question.

Hurray! I remember you, trigonometry. It’s not that hard after all. Finally, I’ve got my graph in order:

Graph with correct shadow for timer

Making the timer more accurate

Now, I’ve got one serious problem.

I’ve always known that setInterval(), is inaccurate, but I didn’t realise how inaccurate it is! When I ran my timer in one tab while I was working in another tab, it lost minutes of time over the span of an hour. That’s terrible.

I had to do something a little different with the fundamental timer implementation.

Instead of just counting down my remaining time by 1 every time setInterval calls my code, I have to:

  • Add a timestamp to my state that stores the current time in milliseconds
  • Each time the updateTimeRemaining function is called, it compares whatever the current time in milliseconds is to the original timestamp.
  • Subtract the current millisecond time from the timestamp, and then subtract that from the time remaining.
  • Then continue as before.

In the code, this is how it looks:

updateTimeRemaining() {
  const millisNow = new Date().getTime();
  const elapsedMillis = millisNow - this.state.lastTimestamp;
  const updatedTimeRemaining = this.state.timeRemaining - elapsedMillis;

  this.setState({ lastTimestamp: millisNow });

  if (updatedTimeRemaining <= 0) {
    // stop timing (removed for brevity)
  } else {
    // update the timer (removed for brevity)
  }
}

Now the timer is accurate. Or accurate enough for timing my work. I wouldn’t time a rocket-burn with it or anything (because that’s what wristwatches are for!)

Sizing for mobile

You can see that this timer looks like an app. I never really intended to run it on my phone, but I’d set its dimensions to suit a smartphone, and I wanted it free of the usual website clutter.

I’ve sized nearly everything in the CSS using ems, so that the app should scale nicely depending on what the font size is set to.

I originally thought I’d chosen a good size for the app to work well on desktop and mobile, but when I ran it in Chrome’s developer tools with the mobile simulation, this is what I got:

Tiny timer in mobile

Well that won’t fly

So I created some media queries in my CSS to cater for different device sizes. I’ve only been able to try this out of my phone and an ancient iPad, but it seems to work so far:

@media only screen and (min-device-width: 768px) {
  body { font-size: 27pt; }
  button { height: 9em; }
}

@media only screen and (min-device-width: 1024px) {
  body { font-size: 29pt; }
  button { height: 9em; }
}

@media only screen and (min-device-width: 1280px) {
  body { font-size: 14pt; }
  button { height: 5em; }
}

The future for the React Timer application

There’s obviously still plenty to do.

I can only imagine the confusion of first-time users when they encounter the empty graph-area.

And  the cymbal-crash that plays when the timer stops is quite alarming if you’re not expecting it! It should probably be optional 😉 And that’s just the start.

I’ll be adding the timer to github soon, and I’ll link to it once it’s there.

Clearly, there’s a lot more detail we could have gone into here for many of the topics I’ve covered, but I’ll be publishing more in-depth work on those soon!

Have you found this super-hasty recap useful? Let me know if you have, and I’ll keep you up to date with future posts.

By


2 Responses

  1. elijah says:

    Good article thankyou but I’d like more details. Also why did you choose that structure for your project did you use create-react-app.

    • Luke says:

      Hi Elijah, what details would you like? I’ll be doing some more soon on testing React components and advanced CSS styling.

      I didn’t use create-react-app because I was adapting this project. I’d happily use it with a brand new project.

Leave a Reply

Your email address will not be published. Required fields are marked *