1.E.3: World Clock

Learning Objectives

  1. Understand how to install packages in an NPM project and start an NPM app

  2. Understand how to mix JavaScript and HTML syntax with JSX to produce dynamic UIs

  3. Understand how React loads through the DOM on a webpage

  4. Understand how to store state in components and use that state to manipulate UI

  5. Understand how to use component lifecycle methods to perform logic on component mount and unmount

  6. Understand how to encapsulate UI elements in components and pass parameters to them with props

Introduction

We will build a collection of clocks with different time zones to demonstrate the fundamentals of React and Node apps.

Starter Code

Clone starter code

Fork and clone Rocket's World Clock repo (Rocket-themed Create React App). Run npm i to install default packages our app needs to run, and run npm start to start our app and see it in our browser.

Understand starter code

  1. README.md contains instructions for running the app

  2. package.json lists the packages our app needs to run and their versions in dependencies, and the scripts that Create React App provides to run our apps in scripts. We can add as many new dependencies and scripts as we want, but we will not add any for this exercise to keep our app simple. We will not strictly need to, but if you are curious, read package.json docs to understand its other attributes in more detail.

  3. package-lock.json lists all dependencies of packages listed in package.json and their versions. This makes sure all dependency versions are standardised for consistency in running our app. We should never alter this file manually.

  4. node_modules contains all dependency files installed by npm i. We should never modify node_modules directly nor commit it to GitHub because its contents can be re-generated on-demand with npm i.

  5. src contains our source code, i.e. our app logic. src/index.js renders our React app's root component into the root HTML page, and App.js defines our React app logic.

  6. public contains static files to load the app website, including its favicon (icon in tab bar), root HTML page and manifest for SEO purposes.

  7. .gitignore specifies files and folders that we should not commit to Git, such as node_modules.

  8. For Windows:

    1. If your React Application does not reflect saved changes in the browser please refer back to this section to enable a refresh cycle.

Base

Hint: View changes in browser while coding

Run app with npm start to view latest changes in browser while writing app logic. If you haven't already, install packages with npm i from the root of the repo before running npm start.

Mix JavaScript and HTML syntax in JSX

src/App.js contains the root React element in our React app and we will write our clock app logic there. If you have not read about components yet, feel free to ignore the syntax class App extends React.Component for now; we will learn about components and their syntax in coming course days.

Notice the starter code in App.js contains mostly HTML. Let's add JavaScript to it to render a date. Replace the render method with the following.

src/App.js
render() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>{new Date().toString()}</p>
      </header>
    </div>
  );
}

Notice we have declared a new JavaScript Date object from within the <p> tags and told React to render that date as a string in our UI. This is possible because React supports JSX syntax that enables mixing of HTML and JavaScript. Feel free to add more JavaScript elements in JSX with curly braces {} like we did with the date.

Render current date and time every second

src/index.js is where we render the root React element of our React app. Let's observe how we can update it to render the current date and time every second to simulate a clock.

Comment out the code that renders App in index.js and add the following tick function and setInterval function call below it. Save the file to observe changes in the browser.

src/index.js
const root = ReactDOM.createRoot(document.getElementById("root"));
// root.render(<App />);

function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  root.render(element);
}

setInterval(tick, 1000);

Notice our app now displays a digital timestamp that updates every second.

setInterval calls the function in its 1st parameter at the interval specified by the number of milliseconds in its 2nd parameter. In this case it is re-generating the data and time via new Date() and re-rendering the UI every second.

Notice the value of element in tick looks similar to the element returned in the render method in App.js: HTML with a JavaScript date inserted using curly braces {} in JSX.

The difference between index.js and App.js is the setInterval in index.js that calls tick and hence root.render every second.

Comfortable

setInterval inside the App component

Requires students to have reviewed Components and Props, State and Lifecycle sections of React docs

What if our app had more UI elements than just the clock and we did not want to re-render the root element in index.js every second? Re-rendering the root element would be costly because it would re-render all components in our app, not just the clock.

Luckily components have lifecycle methods that run only on component mount, such that we can call setInterval from within a clock-specific React component without re-rendering the root element. We will now move our clock logic into the App component so we do not need to modify the way we render our root element in index.js.

Undo changes in index.js

Undo the changes we made to index.js in the previous section. Our index.js file should now contain the following code below imports.

src/index.js
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);

Add state to App component to store date and time in local state

Add state to our App component that will store the date and time that we will update every second in a state variable date. Instead of calling new Date() in our render method, update render to read date from this.state.date instead. this.state.date will contain the current value of the date variable in component state. Our App component should look like the following.

src/App.js
class App extends React.Component {
  constructor(props) {
    super(props);
    // Initialise component state to contain "date" attribute with current date and time
    this.state = { date: new Date() };
  }

  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          {/* Render date value that is stored in state */}
          <p>{this.state.date.toString()}</p>
        </header>
      </div>
    );
  }
}

You may notice that the date is still static. We will now add the setInterval code to allow our clock to re-render every second from within the App component.

Add component lifecycle methods to update date state every second and teardown state-updating logic when component unmounts

Add 2 methods componentDidMount and componentWillUnmount above the render method inside the App component in App.js. Add a setInterval function call in componentDidMount that updates the date in local state to a new date with this.setState every second. Save the timer ID returned by setInterval in a class variable such as this.timerId, and call clearInterval on that timer ID in componentWillUnmount to tear down the timer.

The clock in our UI should now automatically update every second! Here is a reference solution for this section.

Full reference solution at bottom of page

Rocket exercises will typically have a reference solution at the bottom of each exercise page. We will provide code examples inline for explanation, but otherwise we hope you will attempt the exercises on your own and review reference solutions afterward.

Refactor clock display logic into its own component

Imagine now that we wish to render multiple clocks to represent different time zones. We would like to do this inside App because App is the root component.

Naively we could copy-paste our previous clock logic multiple times in App to achieve this, changing the parameters passed to new Date() in our setInterval callback function to set each date to a different timezone. This would cause much repeated code, violate the DRY (don't repeat yourself) principle, leading to increased chances of bugs in our code.

A better solution would be to encapsulate all clock logic in a new component called Clock, and use the Clock component multiples times in App, passing timezone information as a prop to each Clock component for it to render the date and time of the relevant timezone.

Move clock logic into Clock component

Create a new file Clock.js inside the src folder. Define a new React component Clock inside (feel free to mimic the structure of App.js) with all clock-related logic from App.js. Remember to remove the App-specific HTML tags in the render method (everything other than the p tags with date string), and to export default the Clock component at the bottom of the file.

Remove clock logic from App component

Now that our clock logic is in Clock.js, remove all clock-related logic from App.js such that the App component now only contains the render method with an image in its header.

Import Clock and use it in App

Import our Clock component from App.js with code like the following below the other imports in App.js.

import Clock from "./Clock.js";

Use the Clock component in the render method of App where we used to have our p tags. For now, add a single Clock instance and verify that our clock works when we run our app. Our render method might look like the following.

render() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <Clock />
      </header>
    </div>
  );
}

We now have a clock that we can re-use in anywhere in our app with a single line of code! Here is a reference solution for this section

Add timezone data to Clock via props

We will now add multiple clocks, each with a different time zone. Because clock logic for different time zones is the same except for time zone specification, we would not want to create a separate Clock component for each time zone. Instead, we will modify Clock to accept time zone as a prop and render time according to the specified time zone. We will then declare multiple Clocks with different time zones in App.

Research how to display different time zones with JavaScript Dates

JavaScript Dates do not store dates in a specific time zone, but are able to render the date that they store in any time zone with a built-in toLocaleString method. We can use toLocaleString like the following.

const date = new Date();
date.toLocaleString('en-GB', { timeZone: 'Asia/Singapore' })

The first parameter to toLocaleString is a language code (see all legal language codes here), and the 2nd parameter is an options object that allows us to specify time zone (see all valid time zones here in "TZ database name" column).

Now that we know how to render a date in a specific time zone, we can accept a time zone string as a prop and use it to customise Clock!

Update rendered date string in Clock to use toLocaleString with time zone prop

Update the render method in our Clock component to render the date with toLocaleString instead of toString. Pass a language code (whichever you prefer) and time zone option as parameters to toLocaleString, where the time zone comes from props via this.props.timeZone.

In App.js, update our App component to render 3 clocks, each with a different time zone. Specify time zones with props like in the below code snippet. Feel free to pick whichever time zones are most relevant to you!

<Clock timeZone="Asia/Singapore" />

Add time zone label to each clock

To make it clearer which time zone each clock is rendering, add a time zone label next to the date string in Clock's render method. This can be any string that represents the time zone.

Great job on making a clock app that shows multiple time zones! Here is a reference solution for this section. Don't forget to review the reference solution at the bottom of the page to see how Rocket implemented our full app. Coding is like writing an essay and there are many right answers, so don't fret if yours looks different.

Improve clock UI with React Bootstrap grid system

Render time zone and time in separate columns with React Bootstrap's grid system to make our clock information easier to parse. We may wish to implement the grid system and time zone labels in App.js such that our Clock component can just render the time for the relevant time zone.

The grid might look something like the following.

CityClock

Los Angeles

<Clock timeZone="America/Los_Angeles" />

London

<Clock timeZone="Europe/London" />

Singapore

<Clock timeZone="Asia/Singapore" />

More Comfortable: WorldClock component with dynamic number of clocks

Refactor our world clock UI into its own component WorldClock in its own file such that others can use it to create world clock UIs with a custom number of clocks with a custom set of timezones. WorldClock should accept a clockData prop that is an array of time zone strings, where each string corresponds to a new clock. WorldClock should use the Clock component internally, and our App component should import and use WorldClock. You may find the upcoming reading in Lists and Keys helpful for mapping an array of time zone strings to Clock components.

Submission

Submit a pull request to the main branch of Rocket's World Clock repo and share your PR link in your section Slack channel.

If you would like to deploy your app to the internet, follow Create React App GitHub Pages deployment instructions here.

Reference Solution

Here is reference code and a reference deployment for this exercise. You can do better!

Last updated